1 Introduction

Ceci est l’analyse pour la tâche d’analyse de classification multi-classe sur la dataset “Indoor User Movement Prediction from RSS data Data Set”.

L’objectif est de prédire le pattern des mouvements des utilisateurs dans un environmment de travail à travers des séries temporelles générées par un réseau de capteurs sans-fils. Les données contiennent des streams de données temporelles, consistant en la force des signaux radios mesurés entre les noeuds du réseau de capteurs sans-fils, à une fréquence de 8 Hz remappées dans l’intervalle [-1, 1].

Ce réseau se compose de 5 capteurs:

  • 4 ancrées dans l’environnement
  • 1 ancrée sur l’utilisateur

La tache nécessite de prédire la classe indiquant la trajectoire d’un utilisateur, parmi 6 mouvements spécifiques.

On dispose de 314 séquences, pour un nombre total de 13197 étapes. De fait, les séries n’ont pas forcément la même durée.

A noter : la première dataset ne possède pas la classe 3 due à des contraintes physiques. En conséquence, il sera intéressent de vérifier toute différence potentielle entre les prédictions selon les datasets utilisées pour l’entrainement/validation.

Note: le CSS a été modifié manuellement. La première occurrence de “840” (ou “1200” pour le notebook) est remplacée par 1240 (ou “1600” pour le notebok) pour mieux couvrir l’espace horizontal. Vous pouvez modifier le CSS manuellement dans le cas où votre définition d’écran est trop faible pour 1240 (ou 1600) pixels.

L’intégralité des modèles et datasets intermédiaires sont exportées par le script.

1.1 Préparation

Avant de commencer toute recherche, nous devons charger les données en mémoire. De plus, nous devons mettre l’encoding de R en UTF-8, puisqu’on utilise des caractères interdits au format ISO-8859-1. Cela se fait tout simplement dans RStudio via Tools > Global Options > Code > Saving > Default Text Encoding.

1.1.1 Chargement des libraries

Les informations ci-dessous nous permettront de re-créer l’environnement dans lequel on travaille. Pour information, la configuration hardware est la suivante :

  • Processeur : Intel i7-3930K (12 sockets virtualisés)
  • RAM : 54 GB (150 GB swap)
  • Carte graphique : aucune

De même, au niveau software :

  • Système d’exploitation : Windows Server 2012 R2
  • R: Microsoft R Client, version 3.3.2 de R + Intel MKL
  • RStudio Preview, version supérieure ou égale à 1.0.136 (résolution du problème d’encoding des caractères en Markdown)
  • Rtools pour R 3.3.x pour la compilation de packages pour R
  • MinGW 6.2.0 pour la compilation de code C++
  • Java 8 Update 111 pour l’utilisation de H2O
  • Git Bash pour l’utilisation de Bash (pour MinGW)
# Chargement de données
library(data.table)
# Régression linéaire en C++
library(RcppArmadillo)
# htmlwidgets / 3djs / Plotly
library(plotly)
# htmlwidgets / datatables / formattable
library(DT)
library(formattable)
# Graphique tableplot
library(tabplot)
# Machine Learning généraliste en C++
library(xgboost)
# Machine Learning généraliste en Java
library(h2o)
localH2O = h2o.init(nthreads = 1, max_mem_size = "4G") # 1 Thread (reproduction des résultats), 4GB de mémoire vive
h2o.no_progress() # Cache la barre de progression
# Optimiseur continu et discret
library(CEoptim)
# Timing
library(R.utils)
# Plein d'utilitaires
library(Laurae)
# Fonction de timing : prend un temps passé et une tâche, affiche la différence de temps au centième de seconde
timing <- function(timer, what = "?") {
  cat("Temps d'exécution de la tâche '", what, "' : ", sprintf("%.02f", ((System$currentTimeMillis() - timer) / 1000)), " secondes.  \n", sep = "") # Double espace pour éviter le bug d'espace en Markdown R
}
# Informations sur la session
sessionInfo()

1.1.2 Chargement des données

Pour charger les données, qui se trouvent de manière séparée dans des fichiers, nous devons les ouvrir un par un. Cela se fait de manière triviale sur R par une boucle:

  • Nous avons 314 fichiers numérotés de 1 à 314 (et non pas 001 à 314) sous la forme MovementAAL_RSS_X.csv (remplacer X) dans le dossier /dataset, dont la première colonne possède le caractère dièse en trop (pour marquer les colonnes) qui doit être enlevée
  • Nous avons les labels dans le fichier nommé MovementAAL_target.csv dans le dossier /dataset
  • Les données annexes de famille de mouvements (MovementAAL_DatasetGroup.csv) et de chemins (MovementAAL_Paths.csv) sont dans le dossier /group
# Compteur de temps
CurrentTime <- timer() # Chunk Chargement des données en mémoire
# Pré-initialisation des variables
data_pre <- list()
# Chargement des données
for (i in 1:314) {
  data_pre[[i]] <- fread(paste0("dataset/MovementAAL_RSS_", i, ".csv"), sep = ",", verbose = FALSE, showProgress = FALSE, col.names = c("RSS_anchor1", "RSS_anchor2", "RSS_anchor3", "RSS_anchor4"))
}
# Chargement des données annexes
labels <- fread("dataset/MovementAAL_target.csv", sep = ",", verbose = FALSE, showProgress = FALSE, col.names = c("sequence_ID", "class_label"))
labels$class_label[labels$class_label == -1] <- 0
group_room <- fread("groups/MovementAAL_DatasetGroup.csv", sep = ",", verbose = FALSE, showProgress = FALSE, col.names = c("sequence_ID", "dataset_ID"))
group_path <- fread("groups/MovementAAL_Paths.csv", sep = ",", verbose = FALSE, showProgress = FALSE, col.names = c("sequence_ID", "path_ID"))
# Temps nécessaire
timing(CurrentTime, "Chargement des données en mémoire")
Temps d'exécution de la tâche 'Chargement des données en mémoire' : -0.53 secondes.  

1.2 Première Analyse Exploratoire

1.2.1 Tentative de compréhension du problème : problème inhérent aux données

Pour tenter de comprendre le problème, on va prendre les 16 derniers points de chaque série temporelle, et les afficher via 3djs via l’interface Plotly.

Il est clair et net que la Salle 1 démontre un comportement irrégulier par rapport aux deux autres salles. Une analyse trajectoire par trajectoire montre que la :

  • Trajectoire 1 : canal opposé sur l’ancre 1, 2, 3, et 4
  • Trajectoire 2 : canal opposé sur l’ancre 1, 2, 4, avec un gain faible sur l’ancre 3
  • Trajectoire 3 : inexistance (comme prévu, d’après les énoncés - représenté par une ligne bleue fixée à 0)
  • Trajectoire 4 : canal opposé sur l’ancre 1, 2, 4, avec un gain fort sur l’ancre 3
  • Trajectoire 5 : canal opposé sur l’ancre 1, 2, 3, et 4
  • Trajectoire 6 : canal opposé sur l’ancre 1, 2, 3, et 4

Il faudra obligatoirement réaliser une transformation sur les données de la salle 1 afin que les modèles puissent être évalués de manière fiable. Il est tout à fait possible que lors de la collecte des données, les salles 1 et 3 ont été inversées, que des ancres ont été mal positionnées, ou encore qu’il y a eu une erreur de manipulation des données pour créer les datasets par salle. Vu le problème, aucune des trois hypothèses n’est validée ni exclue.

En réalité, l’image suivante, d’après la recherche An experimental characterization of reservoir computing in ambient assisted living applications (Bacciu et al.,2012) explique la raison de la disparité entre la salle 1 et les deux autres salles!

Recherche de Bacciu et al.

Recherche de Bacciu et al.

Il serait intéressant de travailler à la fois sur :

  • L’inversion des ancres A1/A3 et A2/A4
  • La tentative de reproduction des résultats des quatre ancres
# Compteur de temps
CurrentTime <- timer() # Chunk Agrégation des données
# Pré-initialisation de la série temporelle dépivotée par le label
# 
# Archictecture de la matrice (dépivotée sans la salle) 384x4 (64 observations par label, 16 observations par ancre par label) :
# ID  Strength    Anchor      Label       Time
#  1    val_a1         1          1          1
#  2    val_a2         1          1          2
#  ...
# 16   val_a16         1          1         16
# 17   val_a17         2          1          1
#  ...
# 32   val_a32         2          1         16
# 33   val_a33         3          1          1
#  ...
# 48   val_a48         3          1         16
# 49   val_a49         4          1          1
#  ...
# 64   val_a16         4          1         16
# 65   val_a17         1          2          1
#  ...
# ID(anchor, label, time) = ((label - 1) * 64) + ((anchor - 1) * 16) + time
avg_series <- data.table(matrix(rep(0, 16 * 4 * 6 * 6), nrow = 16 * 4 * 6, ncol = 4))
colnames(avg_series) <- c("Strength", "Anchor", "Label", "Time")
# Pré-filling des facteurs (Anchor, Label, Time)
avg_series[["Anchor"]] <- as.factor(rep(inverse.rle(list(lengths = rep(16, 4), values = 1:4)), 6))
levels(avg_series[["Anchor"]]) <- paste("Ancre", 1:4, sep = "")
avg_series[["Label"]] <- as.factor(inverse.rle(list(lengths = rep(16 * 4, 6), values = 1:6)))
levels(avg_series[["Label"]]) <- paste("Trajectoire", 1:6, sep = "")
avg_series[["Time"]] <- rep(1:16, 6 * 4)
# Transformation en liste par salle à dépivoter par la suite
avg_series <- list(cbind(avg_series, Room = rep(1, 384)),
                   cbind(avg_series, Room = rep(2, 384)),
                   cbind(avg_series, Room = rep(3, 384)))
# Pré-compte du nombre d'occurrence des labels
label_count <- list(tabulate(group_path[["path_ID"]][group_room[["dataset_ID"]] == 1]),
                    tabulate(group_path[["path_ID"]][group_room[["dataset_ID"]] == 2]),
                    tabulate(group_path[["path_ID"]][group_room[["dataset_ID"]] == 3]))
# Détermination des 16 dernières observations (2 secondes à 8 Hz), moyennisées
for (i in 1:314) {
  temp_label <- ((group_path[["path_ID"]][i] - 1) * 64) + 1 # Ligne de démarrage dans la matrice agrégée
  temp_obs <- nrow(data_pre[[i]]) - 15 # Ligne de démarrage dans la matrice à sauvegarder
  
  avg_series[[group_room[["dataset_ID"]][i]]][temp_label:(temp_label + 63), 1] <- avg_series[[group_room[["dataset_ID"]][i]]][temp_label:(temp_label + 63), 1] + (unlist(data_pre[[i]][temp_obs:(temp_obs + 15), ]) / label_count[[group_room[["dataset_ID"]][i]]][group_path[["path_ID"]][i]])
}
# Dépivotage de la variable définissant la salle
avg_series <- rbind(avg_series[[1]], avg_series[[2]], avg_series[[3]])
avg_series[["Room"]] <- as.factor(inverse.rle(list(lengths = rep(384, 3), values = 1:3)))
levels(avg_series[["Room"]]) <- paste("Salle", 1:3, sep = "")
# Affichage sous forme de plot interactif de manière automatisée
ggplotly(ggplot(data = avg_series, aes_string(x = "Time", y = "Strength", group = "Label", color = "Label")) + geom_line() + geom_point() + scale_color_brewer(palette = "Set2") + theme_bw() + facet_grid(Anchor ~ Room) + labs(title = "Evolution de la force du signal de l'ancre par rapport au temps"), width = 960, height = 720)

# Enregistrement de la table pour usage ultérieur si nécessaire
fwrite(avg_series, "agregation/agregation1.csv")
# Temps nécessaire
timing(CurrentTime, "Agrégation des données")
Temps d'exécution de la tâche 'Agrégation des données' : 3.79 secondes.  

1.2.2 Création de features

Pour créer un benchmark rapide basé sur les données et s’accomoder des tailles différentes des variables, on créé les différentes (36) features :

  • Coefficient linéaires multiplicateurs par modèle linéaire entre chaque ancre (trois ancres estiment une ancre) (16 features) => ces features doivent pouvoir répondre à la direction linéaire probable de la personne, par ancre
  • Résidus des modèles linéaires (16 features) => ces features doivent annoncer l’incertitude linéaire de la personne, par ancre
  • Le dernier point avant d’observation enregistré (4 features) => ces features doivent permettre de positionner la personne juste avant sa trajectoire future

On évitera de tenter de prédire le mouvement d’une personne au niveau local 8 secondes plus tard, puisqu’on ne dispose pas forcément d’un jeu de données large par série temporelle (sans oublier que les quatre ancres sont dépendentes l’une de l’autre, donc prédire l’une nécessite de prédire les trois autres également et simultanément si possible).

# Compteur de temps
CurrentTime <- timer() # Chunk Création des features
# Pré-initialisation de la frame
mini_lm <- data.frame(matrix(nrow = 314, ncol = 36))
# Boucle par série temporelle
for (i in 1:314) {
  
  # Boucle par ancre
  for (j in 1:4) {
    
    # Entrainement d'un modèle linéaire utilisant les autres ancres, avec l'interceptrice
    temp_model <- fastLmPure(X = cbind(as.matrix(data_pre[[i]][, (1:4)[-j], with = FALSE]), rep(1, nrow(data_pre[[i]]))), y = data_pre[[i]][[j]])
    
    # Enregistrement des coefficients et des résidus
    mini_lm[i, (j * 8 - 7):(j * 8)] <- c(temp_model$coefficients, temp_model$stderr)
    
  }
  
  # Ajout du dernier élément de la série temporelle (4 ancres)
  mini_lm[i, 33:36] <- data_pre[[i]][nrow(data_pre[[i]]), ]
  
}
# Enregistrement des données au format CSV
fwrite(cbind(mini_lm, Group = group_room[["dataset_ID"]], Label = group_path[["path_ID"]]), "features/features1.csv")
# Temps nécessaire
timing(CurrentTime, "Création des features")
Temps d'exécution de la tâche 'Création des features' : 2.25 secondes.  

1.2.3 Feature Map

On enregistre le nom des features dans un fichier à part (Feature map = “Vecteur de données => Espace de features”, ici une colonne = une feature). Il est utile de faire correspondre des colonnes à des variables, surtout lorsque parfois on utilise des techniques de remapping (exemple : kernel trick).

cat(c(paste0(rep(c(paste0("Coef", 1:4), paste0("Rési", 1:4)), 4), paste0("_", inverse.rle(list(lengths = rep(8, 4), values = 1:4)))), paste0("PosInitiale_", 1:4)), sep = ", ", file = "features/map.csv", append = FALSE)

2 Première Analyse Systémique

L’analyse systémique porte sur la performance des modèles en utilisant leurs paramètres par défaut, en couvrant si possible plusieurs différent types de modèles :

Type de modèle Description Wrapper
Linéaire Régression logistique (on utilisera le modèle linéaire généralisé) xgboost (C++), H2O (Java)
Non-linéaire sans ensemble Arbre de décision (on utilisera une itération de boosting d’arbres de décision) xgboost (C++), H2O (Java)
Non-linéaire d’underfitting Arbre de décision (on utilisera le modèle de Random Forest) xgboost (C++), H2O (Java)
Non-linéaire d’overfitting Arbre de décision (on utilisera le modèle d’arbres de décision boosté) xgboost (C++), H2O (Java)
Linéaire d’overfitting Réseaux de neurones (on testera une architecture 32x6 + ReLU / Tanh, 16x16x6 + ReLU / Tanh) H2O (Java)

Par simplicité, on utilisera xgboost, qui est un modèle codé en C++ dont la vitesse de calcul (et l’essentiel de la paramétrisation) est inégalée (mis à part LightGBM par Microsoft), ainsi que H2O codé en Java (relativement beaucoup plus lent, même si plus rapide que la plupart des libraries de machine learning). Nous ne sauvegardons pas encore de modèles. Ces deux libraries disposent de modèles exportables qui peuvent être enregistrés et déployés respectivement en C/shell (xgboost) et Java (H2O) pour un usage immédiat.

Nous n’utilisons pas un KNN (voisins les plus proches) ni un SVM (Support Vector Machines) pour leurs problèmes d’entrainement et de déploiement (KNN défère l’entrainement à la prédiction, et SVM a une complexité de calcul telle que ce ne sont pas des modèles déployables de manière efficiente en entreprise).

Les objectifs seront les suivants :

  • Minimiser l’erreur de classification
  • Ne pas dépasser 25% de features (52) par rapport au nombre d’observations par fold pour éviter le surapprentissage des features (qui ne se repérera pas sur les métriques de validation avec si peu d’échantillons, qui est le problème de fluctuation par erreur)
  • Dépasser le seuil du modèle “aléatoire” (53/210 + 53/208 + 52/210, soit 05.02f%)

On prépare d’abord les pré-requis pour le calcul, qui sont :

  • Les folds à utiliser pour la cross-validation : on découpera de manière disjointe les salles, afin d’éviter tout leakage à l’intérieur des salles (afin d’empêcher le modèle de machine learning d’apprendre les salles et non pas la généralisation à des échantillon inconnus dans de nouvelles salles)
  • La métrique d’évaluation, où ici l’exactitude des prédictions est multi-classe

Comme nous allons découper selon différentes méthodes, nous devrons créer à la fois les fold de train mais aussi de test :

  • Entrainement sur une salle, prédiction sur une autre salle (1 contre 1)
  • Entrainement sur une salle, prédiction sur deux autres salles (1 contre 2)
  • Entrainement sur deux salles, prédiction sur une autre salle (2 contre 1)

La moyenne générale sera agrégée à partir des trois moyennes agrégées (1 contre 1, 1 contre 2, et 2 contre 1). Au total, nous avons 144 modèles (12 modèles, 12 sets d’entrainement/validation) à tester. Vu la taille des données (314 observations, moins d’une centaine de features) et la mémoire vive disponible (plus de 50 GB), on peut se permettre de pré-créer toutes les datasets au lieu de les créer à la volée.

Le temps perdu à la création des datasets ici est due en grande partie due à H2O, dont les objets doivent être traduits en Java à partir de R.

Les fichiers sont enregistrés avec la nomenclature suivante :

  • NL pour “No Label” (données non labellisées)
  • L pour “Label” (données labellisées)
  • .csv pour le format CSV (Comma-Separated Values)
  • .data pour le format binaire xgboost (LightSVM compressé)
# Compteur de temps
CurrentTime <- timer() # Chunk Préparation de l'évaluation du modèle
# Où sauvegarder les fichiers ?
file_tag <- "1_data/"
# Initialisation de la variable qui accueillera la précision
accuracy <- data.frame(matrix(nrow = 16, ncol = 13))
colnames(accuracy) <- c("Fold", "xgb_LinearModel", "xgb_DecisionTree", "xgb_RandomForest", "xgb_GradientBoosting", "h2o_LinearModel", "h2o_DecisionTree", "h2o_RandomForest", "h2o_GradientBoosting", "h2o_NN_32x6_ReLU", "h2o_NN_32x6_Soft", "h2o_NN_16x16x6_ReLU", "h2o_NN_16x16x6_Soft")
accuracy[, 1] <- c("Fold_1v2", "Fold_1v3", "Fold_2v1", "Fold_2v3", "Fold_3v1", "Fold_3v2", "Fold_1v23", "Fold_2v13", "Fold_3v12", "Fold_12v3", "Fold_13v2", "Fold_23v1", "Moyenne_1c1", "Moyenne_1c2", "Moyenne_2c1", "Moyenne")
# Initialisation des folds pour la cross-validation
folds_train <- list()
folds_test <- list()
training_data <- list()
testing_data <- list()
training_xgb <- list()
testing_xgb <- list()
training_h2o <- list()
testing_h2o <- list()
combinations_train <- c(list(1, 1, 2, 2, 3, 3), combn(3, 1, simplify = FALSE), combn(3, 2, simplify = FALSE))
combinations_test <- c(list(2, 3, 1, 3, 1, 2), rev(combn(3, 2, simplify = FALSE)), rev(combn(3, 1, simplify = FALSE)))
temp_factors <- as.factor(group_path$path_ID)
# Création des données d'entrainement et de validation
for (i in 1:12) {
  
  # Création des folds d'entrainement et de validation
  folds_train[[i]] <- which(group_room[["dataset_ID"]] %in% combinations_train[[i]])
  folds_test[[i]] <- which(group_room[["dataset_ID"]] %in% combinations_test[[i]])
  
  # Recherche et suppression du label 3 lorsque la salle 1 est isolée (soit en train on enlève en test, soit en test on enlève en train)
  if ((length(combinations_train[[i]]) == 1) & (combinations_train[[i]][1] == 1)) {
    folds_test[[i]] <- folds_test[[i]][group_path$path_ID[folds_test[[i]]] != 3]
  }
  if ((length(combinations_test[[i]]) == 1) & (combinations_test[[i]][1] == 1)) {
    folds_train[[i]] <- folds_train[[i]][group_path$path_ID[folds_train[[i]]] != 3]
  }
  
  # Création des données d'entrainement et de validation
  training_data[[i]] <- mini_lm[folds_train[[i]], ]
  testing_data[[i]] <- mini_lm[folds_test[[i]], ]
  
  # Enregistrement des données CSV
  fwrite(training_data[[i]], paste0(file_tag, "trainNL_", sprintf("%02d", i), ".csv"))
  fwrite(testing_data[[i]], paste0(file_tag, "testNL_", sprintf("%02d", i), ".csv"))
  
  # Transformation des données au format approprié pour xgboost
  training_xgb[[i]] <- xgb.DMatrix(data = as.matrix(training_data[[i]]), label = group_path$path_ID[folds_train[[i]]] - 1)
  testing_xgb[[i]] <- xgb.DMatrix(data = as.matrix(testing_data[[i]]), label = group_path$path_ID[folds_test[[i]]] - 1)
  
  # Dumping des datasets binaires xgboost
  xgb.DMatrix.save(training_xgb[[i]], paste0(file_tag, "trainL_", sprintf("%02d", i), ".data"))
  xgb.DMatrix.save(testing_xgb[[i]], paste0(file_tag, "testL_", sprintf("%02d", i), ".data"))
  
  # Transformation des données au format approprié pour H2O
  training_h2o[[i]] <- as.h2o(cbind(Label = temp_factors[folds_train[[i]]], training_data[[i]]))
  testing_h2o[[i]] <- as.h2o(cbind(Label = temp_factors[folds_test[[i]]], testing_data[[i]]))
  
  # Enregistrement des frames H2O (CSV + Label)
  h2o.exportFile(training_h2o[[i]], paste0(file_tag, "trainL_", sprintf("%02d", i), ".csv"), force = TRUE)
  h2o.exportFile(testing_h2o[[i]], paste0(file_tag, "testL_", sprintf("%02d", i), ".csv"), force = TRUE)
  
}
# Temps nécessaire
timing(CurrentTime, "Préparation de l'évaluation du modèle")
Temps d'exécution de la tâche 'Préparation de l'évaluation du modèle' : 20.64 secondes.  

Dans le cas où on souhaiterait faire une classification binaire, utiliser un seuil dynamique pour découper les labels est plus adéquat (par rapport à 0.50 comme seuil). On ne réalise pas de classification binaire ici, mais cela peut toujours servir dans le futur.

# Le backend utilisé par xgboost pour déterminer le seuil à utiliser pour partager les labels 0 et 1
acc_eval <- function(pred, dtrain) {
  
  # Récupération du label
  y_true <- getinfo(dtrain, "label")
  
  # Création de la data.table triée avec comme clé primaire la probabilité
  DT <- data.table(y_true = y_true, y_prob = pred, key = "y_prob")
  
  # Préparation pour le nettoyage des doublons postérieurs
  cleaner <- !duplicated(DT[, "y_prob"], fromLast = TRUE)
  
  # Pré-calcul de variables spécifiques
  lens <- length(y_true)
  nump <- sum(y_true)
  
  # Détermination des vrais négatifs et des vrais positifs
  DT[, tn_v := cumsum(y_true == 0)]
  DT[, tp_v := nump - cumsum(y_true == 1)]
  
  # Nettoyage des doublons pour éviter le problème d'ordre par chance
  DT <- DT[cleaner, ]
  
  # Détermination de l'exactitude des données
  DT[, acc := (tn_v + tp_v) / lens]
  
  # Annulation à zéro pour toute observation dont le calcul aboutit à une erreur
  DT[, acc := ifelse(!is.finite(acc), 0, acc)]
  
  # Recherche de la meilleure exactitude
  best_row <- which.max(DT$acc)
  best_acc <- round(100 * DT$acc[best_row[1]], digits = 8)
  
  # Retour de la meilleure exactitude
  return(list(metric = "acc", value = best_acc))
  
}

2.1 Entraînement des douze modèles

Le temps de calcul est principalement du aux modèles H2O qui prennent la majorité du temps de calcul (environ 180 secondes contre quelques secondes pour les 12x4 xgboost). Chaque modèle est entrainé sur un set d’entrainement, et testé sur un set de validation.

Pour utiliser les fichiers Java, il faut utiliser le fichier h2o-genmodel.jar adéquat trouvable ici : http://mvnrepository.com/artifact/ai.h2o/h2o-genmodel

Note : les modèles enregistrés ne sont pas forcément les meilleurs, mais contiennent le modèle final entrainé (avec potentiellement de l’overfitting).

# Compteur de temps
CurrentTime <- timer() # Création et évaluation des douze modèles de benchmark
# Où sauvegarder les fichiers ?
file_tag <- "1_models/"
file_h2o <- "1_models"
xgb_dynamic_train <- function(train, test, booster, nrounds, num_parallel_trees) {
  
  # Fixation du seed du générateur de nombres aléatoires pour qu'on puisse reproduire les résultats sur d'autres machines de manière exacte
  set.seed(11111)
  
  # Entrainement du modèle
  return(xgb.train(data = train,
                   num_class = 6, # Classification à 6 classes
                   nthread = 1, # 1 coeur utilisé
                   nrounds = nrounds, # Nombre d'itérations de boosting
                   num_parallel_trees = num_parallel_trees, # Nombre d'arbres pour le mode Random Forest
                   subsample = ifelse(num_parallel_trees > 1, 0.632, 1), # Bootstrap des données pour l'échantillonnage en mode Random Forest
                   eta = 0.10, # Shrinkage pour le boosting
                   booster = booster, # Type d'entrainement : linéaire ou non-linéaire
                   objective = "multi:softprob", # Gradient/Hessian pour l'optimisation par Gradient Descent
                   eval_metric = "merror", # Inexactitude de la classification
                   maximize = FALSE, # Minimisation de l'erreur
                   early_stopping_rounds = 100, # Arrêt après 100 itérations sans amélioration de la métrique
                   verbose = FALSE, # Sans print des itérations
                   watchlist = list(test = test), # Estimation sur les données de test
                   callbacks = list(cb.evaluation.log()))) # Logging des données d'entrainement pour pouvoir récupérer les métriques
}
h2o_nn_train <- function(train, test, model_id, activation, hidden) {
  
  return(temp_model <- h2o.deeplearning(y = 1, # Label = 1ère colonne
                                        training_frame = train,
                                        validation_frame = test,
                                        model_id = model_id, # Nom du modèle
                                        standardize = FALSE, # Pas de standardisation des données, puisque [-1, 1]
                                        activation = activation, # Activation finale du réseau de neurones
                                        hidden = hidden, # Architecture du réseau de neurones
                                        epochs = 100, # Nombre de passes
                                        loss = "CrossEntropy", # Optimisation Softmax
                                        distribution = "multinomial", # Classification multi-class
                                        stopping_rounds = 10, # Arrêt après 10 itérations sans amélioration spécifique
                                        stopping_metric = "misclassification", # Minimisation de l'erreur
                                        stopping_tolerance = 0.00001, # Tolérance maximale de 0.001% de stagnation de l'erreur
                                        reproducible = TRUE, # Tentative de résultats reproductibles
                                        seed = 0)) # Reproduction des résultats
  
}
# Boucle d'évaluation
for (i in 1:12) {
  
  # Entrainement du modèle de régression logistique (xgboost)
  temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
                                  test = testing_xgb[[i]],
                                  booster = "gblinear", # Linéaire
                                  nrounds = 1000000, # Arrêté au meilleur résultat
                                  num_parallel_trees = 1)
  xgb.dump(model = temp_model, # Modèle à enregistrer
           fname = paste0(file_tag, "xgb_glm_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
           with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
           dump_format = "json") # Dump au format json, ré-utilisable
  accuracy[i, 2] <- 1 - temp_model$evaluation_log[[2]][temp_model$best_iteration] # Récupération du meilleur résultat
  
  # Entrainement du modèle d'arbre de décision (xgboost)
  temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
                                  test = testing_xgb[[i]],
                                  booster = "gbtree", # Non-linéaire
                                  nrounds = 1, # Un seul arbre
                                  num_parallel_trees = 1)
  xgb.dump(model = temp_model, # Modèle à enregistrer
           fname = paste0(file_tag, "xgb_dt_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
           with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
           dump_format = "json") # Dump au format json, ré-utilisable
  accuracy[i, 3] <- 1 - temp_model$evaluation_log[[2]][1] # Récupération du meilleur résultat
  
  # Entrainement du modèle de Random Forest (xgboost)
  temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
                                  test = testing_xgb[[i]],
                                  booster = "gbtree", # Non-linéaire
                                  nrounds = 1, # Une seule itération
                                  num_parallel_trees = 200) # De 200 arbres
  xgb.dump(model = temp_model, # Modèle à enregistrer
           fname = paste0(file_tag, "xgb_rf_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
           with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
           dump_format = "json") # Dump au format json, ré-utilisable
  accuracy[i, 4] <- 1 - temp_model$evaluation_log[[2]] # Récupération du meilleur résultat
  
  # Entrainement du modèle d'arbre de décision boosté avec protection contre l'overfitting (xgboost)
  temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
                                  test = testing_xgb[[i]],
                                  booster = "gbtree", # Non-linéaire
                                  nrounds = 1000000, # Arrêté au meilleur résultat
                                  num_parallel_trees = 1)
  xgb.dump(model = temp_model, # Modèle à enregistrer
           fname = paste0(file_tag, "xgb_gbt_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
           with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
           dump_format = "json") # Dump au format json, ré-utilisable
  accuracy[i, 5] <- 1 - temp_model$evaluation_log[[2]][temp_model$best_iteration] # Récupération du meilleur résultat
  
  # Entrainement du modèle de régression logistique (h2o)
  temp_model <- h2o.glm(y = 1,
                        training_frame = training_h2o[[i]],
                        validation_frame = testing_h2o[[i]],
                        model_id = paste0("h2o_glm_", sprintf("%02d", i)), # Nom du modèle
                        max_iterations = 100, # 100 itérations d'optimisation
                        solver = "IRLSM", # Solveur par défaut
                        standardize = FALSE, # Pas de standardisation puisque [-1, 1]
                        family = "multinomial", # Classification multi-classe
                        seed = 0, # Reproduction des résultats
                        intercept = TRUE)
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 6] <- temp_model@model$validation_metrics@metrics$hit_ratio_table[1, 2]
  
  # Entrainement du modèle d'arbre de décision (h2o)
  temp_model <- h2o.randomForest(y = 1,
                                 training_frame = training_h2o[[i]],
                                 validation_frame = testing_h2o[[i]],
                                 model_id = paste0("h2o_dt_", sprintf("%02d", i)), # Nom du modèle
                                 sample_rate = 1, # Toutes les observations seront prises en compte pour le seul arbre de décision
                                 mtries = 36, # Toutes les features seront prises en compte pour le seul arbre de décision
                                 ntrees = 1, # Un seul arbre
                                 seed = 0) # Reproduction des résultats
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 7] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
  # Entrainement du modèle de Random Forest (h2o)
  temp_model <- h2o.randomForest(y = 1,
                                 training_frame = training_h2o[[i]],
                                 validation_frame = testing_h2o[[i]],
                                 model_id = paste0("h2o_rf_", sprintf("%02d", i)), # Nom du modèle
                                 sample_rate = 0.632, # Bootstrapping .632 pour chaque arbre de décision
                                 mtries = -1, # sqrt(36) features seront prises en compte pour chaque arbre de décision
                                 ntrees = 200, # 200 arbres
                                 seed = 0) # Reproduction des résultats
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 8] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
  # Entrainement du modèle d'arbre de décision boosté avec protection contre l'overfitting (h2o)
  temp_model <- h2o.gbm(y = 1,
                      training_frame = training_h2o[[i]],
                      validation_frame = testing_h2o[[i]],
                      model_id = paste0("h2o_gbt_", sprintf("%02d", i)), # Nom du modèle
                      distribution = "multinomial", # Classification multi-classe
                      sample_rate = 1, # Pas de processus stochastique
                      ntrees = 100, # 100 itérations de boosting au maximum
                      score_each_iteration = TRUE, # Noter la valeur de chaque itération
                      stopping_rounds = 10, # Arrêt après 10 itérations sans amélioraton de la métrique
                      stopping_metric = "misclassification", # Surveiller l'inexactitude de la classification pour l'arrêt
                      stopping_tolerance = 0.00001, # Arrêter lorsque la métrique stagne de 0.001%
                      seed = 0) # Reproduction des résultats
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 9] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
  # Entrainement du réseau de neurones à architecture 32x6 + ReLU (h2o)
  temp_model <- h2o_nn_train(train = training_h2o[[i]],
                             test = testing_h2o[[i]],
                             model_id = paste0("h2o_nn_32x6_ReLU_", sprintf("%02d", i)), # Nom du modèle
                             activation = "Rectifier", # ReLU
                             hidden = 32) # Architecture 32x6
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 10] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
  # Entrainement du réseau de neurones à architecture 32x6 + Tanh (h2o)
  temp_model <- h2o_nn_train(train = training_h2o[[i]],
                             test = testing_h2o[[i]],
                             model_id = paste0("h2o_nn_32x6_Tanh_", sprintf("%02d", i)), # Nom du modèle
                             activation = "Tanh", # "Sigmoide"
                             hidden = 32) # Architecture 32x6
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 11] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
  # Entrainement du réseau de neurones à architecture 16x16x6 + ReLU (h2o)
  temp_model <- h2o_nn_train(train = training_h2o[[i]],
                             test = testing_h2o[[i]],
                             model_id = paste0("h2o_nn_16x16x6_ReLU_", sprintf("%02d", i)), # Nom du modèle
                             activation = "Rectifier", # ReLU
                             hidden = c(16, 16)) # Architecture 16x16x6
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 12] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
  # Entrainement du réseau de neurones à architecture 16x16x6 + Tanh (h2o)
  temp_model <- h2o_nn_train(train = training_h2o[[i]],
                             test = testing_h2o[[i]],
                             model_id = paste0("h2o_nn_16x16x6_Tanh_", sprintf("%02d", i)), # Nom du modèle
                             activation = "Tanh", # "Sigmoide"
                             hidden = c(16, 16)) # Architecture 16x16x6
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 13] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
}
# Temps nécessaire
timing(CurrentTime, "Création et évaluation des douze modèles de benchmark")
Temps d'exécution de la tâche 'Création et évaluation des douze modèles de benchmark' : 178.52 secondes.  

2.2 Analyse des résultats pour les douze modèles

Nous remarquons clairement le problème qu’on a soulevé dés le début : la salle 1 possède des features qui ne sont pas adéquates en l’état, les données semble bien inutilisables sans transformation préalable.

De fait, les résultats ne sont pas encore exploitables de manière correcte.

# Moyenne des résultats
for (i in 2:13) {
  accuracy[13, i] <- mean(accuracy[1:6, i])
  accuracy[14, i] <- mean(accuracy[7:9, i])
  accuracy[15, i] <- mean(accuracy[10:12, i])
  accuracy[16, i] <- mean(accuracy[13:15, i])
}
# Enregistrement des scores
fwrite(accuracy, "scores/1_models.csv")
# Affichage des résultats dans un tableau interactif
to_print <- data.table(t(accuracy[13:16, -1])) # Préparation des données à mettre sur table
colnames(to_print) <- c("1 contre 1", "1 contre 2", "2 contre 1", "Moyenne") # Remise des noms des colonnes
row.names(to_print) <- colnames(accuracy)[-1] # Remise des noms des lignes
datatable(to_print,
          filter = "top", # Filtrage au-dessus de la table
          class = "cell-border stripe", # CSS
          extensions = c("ColReorder",
                         "RowReorder"), # Reordonner manuellement à la main
          options = list(pageLength = 12, # Page affichant 12 lignes
                         order = list(list(4, "desc")), # Ordonner par défaut par l'exactitude moyenne
                         colReorder = TRUE, # Plugin
                         rowReorder = TRUE)) %>% # Plugin
  formatStyle(c("1 contre 1", "1 contre 2", "2 contre 1"),
                  background = styleColorBar(c(0, 1), 'lightgreen'), # Couleur vert clair pour les métriques par fold
                  backgroundSize = '100% 90%',
                  backgroundRepeat = 'no-repeat',
                  backgroundPosition = 'center') %>%
  formatStyle("Moyenne",
              background = styleColorBar(c(0, 1), 'pink'), # Couleur rose pour la métrique de moyenne
              backgroundSize = '100% 90%',
              backgroundRepeat = 'no-repeat',
              backgroundPosition = 'center') %>%
  formatPercentage(columns = c("1 contre 1", "1 contre 2", "2 contre 1"),
              digits = 8) %>%
  formatPercentage(columns = "Moyenne",
              digits = 8)

# Affichage des résultats dans un tableau statique
formattable(accuracy[, c(1, 2:5)], list(formattable::area(col = xgb_LinearModel:xgb_GradientBoosting) ~ color_bar("orange")))

formattable(accuracy[, c(1, 6:9)], list(formattable::area(col = h2o_LinearModel:h2o_GradientBoosting) ~ color_bar("cyan")))

formattable(accuracy[, c(1, 10:13)], list(formattable::area(col = h2o_NN_32x6_ReLU:h2o_NN_16x16x6_Soft) ~ color_bar("yellow")))

3 Seconde Analyse Exploratoire

Il faudra préparer correctement les données. Pour cela, deux étapes :

  • Nettoyer les données
  • Créer les features de nouveau

Ensuite, on pourra refaire l’analyse systémique.

3.1 Nettoyage des données

On peut tenter de travailler sur la résolution du problème des signaux mixés, ainsi que le problème de gain.

Supposons que la Salle 2 a bien été pairée avec la Salle 1, alors :

  • Les signaux mixés devraient avoir leur signe inversé (mis à part les fluctuations de gain)
  • Les signaux faibles devraient avoir leur coefficient multiplicateur augmenté (mis à part les fluctuations de gain)

On se retrouve donc avec un modèle de type f(x) = ax+b, qui est un problème linéaire facilement optimisable. Nous allons utiliser la méthode des moindres carrés pour ajuster les valeurs de la Salle 1, à partir de celles de la Salle 2.

Afin de ne pas introduire de leakage, nous travaillerons au niveau global (sans prendre en compte le label) des salles, ce qui n’est pas forcément la meilleure méthode (mais est plus sûre). Les résultats semblent bien meilleurs d’après le graphique. Nous pourrons tester notre hypothèse d’amélioration des résultats dans la prochaine analyse systémique.

# Compteur de temps
CurrentTime <- timer() # Chunk Nettoyage des données
# Sauvegarde des anciennes données par deep copy
data_pre_old <- list()
for (i in 1:314) {
  data_pre_old[[i]] <- copy(data_pre[[i]]) # Replication en mémoire au lieu de la copie du pointeur
}
# Boucle par ancre
for (i in 1:4) {
  
  # Regroupage des données selon l'ancre et la salle
  temp_salle1 <- avg_series[(Anchor == paste0("Ancre", i)) & (Room == "Salle1"), ]$Strength
  temp_salle2 <- avg_series[(Anchor == paste0("Ancre", i)) & (Room == "Salle2"), ]$Strength
  
  # Détermination du modèle linéaire par la méthode des moindres carrés
  temp_model <- fastLmPure(X = cbind(as.matrix(temp_salle1), rep(1, length(temp_salle1))), y = temp_salle2)
  
  # Nettoyage des données d'après le coefficient directeur et l'intersection
  for (j in which(group_room$dataset_ID == 1)) {
    data_pre[[j]][[i]] <- data_pre[[j]][[i]] * temp_model$coefficients[1] + temp_model$coefficients[2]
  }
  
}
# Relancement de l'agrégation des données
avg_series_clean <- data.table(matrix(rep(0, 16 * 4 * 6 * 6), nrow = 16 * 4 * 6, ncol = 4))
colnames(avg_series_clean) <- c("Strength", "Anchor", "Label", "Time")
# Pré-filling des facteurs (Anchor, Label, Time)
avg_series_clean[["Anchor"]] <- as.factor(rep(inverse.rle(list(lengths = rep(16, 4), values = 1:4)), 6))
levels(avg_series_clean[["Anchor"]]) <- paste("Ancre", 1:4, sep = "")
avg_series_clean[["Label"]] <- as.factor(inverse.rle(list(lengths = rep(16 * 4, 6), values = 1:6)))
levels(avg_series_clean[["Label"]]) <- paste("Trajectoire", 1:6, sep = "")
avg_series_clean[["Time"]] <- rep(1:16, 6 * 4)
# Transformation en liste par salle à dépivoter par la suite
avg_series_clean <- list(cbind(avg_series_clean, Room = rep(1, 384)),
                         cbind(avg_series_clean, Room = rep(2, 384)),
                         cbind(avg_series_clean, Room = rep(3, 384)))
# Pré-compte du nombre d'occurrence des labels
label_count <- list(tabulate(group_path[["path_ID"]][group_room[["dataset_ID"]] == 1]),
                    tabulate(group_path[["path_ID"]][group_room[["dataset_ID"]] == 2]),
                    tabulate(group_path[["path_ID"]][group_room[["dataset_ID"]] == 3]))
# Détermination des 16 dernières observations (2 secondes à 8 Hz), moyennisées
for (i in 1:314) {
  temp_label <- ((group_path[["path_ID"]][i] - 1) * 64) + 1 # Ligne de démarrage dans la matrice agrégée
  temp_obs <- nrow(data_pre[[i]]) - 15 # Ligne de démarrage dans la matrice à sauvegarder
  
  avg_series_clean[[group_room[["dataset_ID"]][i]]][temp_label:(temp_label + 63), 1] <- avg_series_clean[[group_room[["dataset_ID"]][i]]][temp_label:(temp_label + 63), 1] + (unlist(data_pre[[i]][temp_obs:(temp_obs + 15), ]) / label_count[[group_room[["dataset_ID"]][i]]][group_path[["path_ID"]][i]])
}
# Dépivotage de la variable définissant la salle
avg_series_clean <- rbind(avg_series_clean[[1]], avg_series_clean[[2]], avg_series_clean[[3]])
avg_series_clean[["Room"]] <- as.factor(inverse.rle(list(lengths = rep(384, 3), values = 1:3)))
levels(avg_series_clean[["Room"]]) <- paste("Salle", 1:3, sep = "")
# Affichage sous forme de plot interactif de manière automatisée
ggplotly(ggplot(data = avg_series_clean, aes_string(x = "Time", y = "Strength", group = "Label", color = "Label")) + geom_line() + geom_point() + scale_color_brewer(palette = "Set2") + theme_bw() + facet_grid(Anchor ~ Room) + labs(title = "Evolution de la force du signal de l'ancre (corrigé) par rapport au temps"), width = 960, height = 720)

# Enregistrement de la table pour usage ultérieur si nécessaire
fwrite(avg_series_clean, "agregation/agregation2.csv")
# Temps nécessaire
timing(CurrentTime, "Nettoyage des données")
Temps d'exécution de la tâche 'Nettoyage des données' : 5.70 secondes.  

3.2 Création des nouvelles features

Nous pouvons désormais créer des features nettoyées. Cela nous servira dans la prochaine analyse systémique.

# Compteur de temps
CurrentTime <- timer() # Chunk Création des features nettoyées
# Pré-initialisation de la frame
mini_lm <- data.frame(matrix(nrow = 314, ncol = 36))
# Boucle par série temporelle
for (i in 1:314) {
  
  # Boucle par ancre
  for (j in 1:4) {
    
    # Entrainement d'un modèle linéaire utilisant les autres ancres, avec l'interceptrice
    temp_model <- fastLmPure(X = cbind(as.matrix(data_pre[[i]][, (1:4)[-j], with = FALSE]), rep(1, nrow(data_pre[[i]]))), y = data_pre[[i]][[j]])
    
    # Enregistrement des coefficients et des résidus
    mini_lm[i, (j * 8 - 7):(j * 8)] <- c(temp_model$coefficients, temp_model$stderr)
    
  }
  
  # Ajout du dernier élément de la série temporelle (4 ancres)
  mini_lm[i, 33:36] <- data_pre[[i]][nrow(data_pre[[i]]), ]
  
}
# Enregistrement des données au format CSV
fwrite(cbind(mini_lm, Group = group_room[["dataset_ID"]], Label = group_path[["path_ID"]]), "features/features2.csv")
# Temps nécessaire
timing(CurrentTime, "Création des features nettoyées")
Temps d'exécution de la tâche 'Création des features nettoyées' : 1.96 secondes.  

4 Seconde Analyse Systémique

Vu que nous disposons de features nettoyées, nous pouvons procéder à l’analyse systémique de zéro. Crééons d’abord les différents jeux de données.

# Compteur de temps
CurrentTime <- timer() # Préparation de l'évaluation du modèle nettoyé
# Où sauvegarder les fichiers ?
file_tag <- "2_data/"
# Initialisation de la variable qui accueillera la précision
accuracy <- data.frame(matrix(nrow = 16, ncol = 13))
colnames(accuracy) <- c("Fold", "xgb_LinearModel", "xgb_DecisionTree", "xgb_RandomForest", "xgb_GradientBoosting", "h2o_LinearModel", "h2o_DecisionTree", "h2o_RandomForest", "h2o_GradientBoosting", "h2o_NN_32x6_ReLU", "h2o_NN_32x6_Soft", "h2o_NN_16x16x6_ReLU", "h2o_NN_16x16x6_Soft")
accuracy[, 1] <- c("Fold_1v2", "Fold_1v3", "Fold_2v1", "Fold_2v3", "Fold_3v1", "Fold_3v2", "Fold_1v23", "Fold_2v13", "Fold_3v12", "Fold_12v3", "Fold_13v2", "Fold_23v1", "Moyenne_1c1", "Moyenne_1c2", "Moyenne_2c1", "Moyenne")
# Initialisation des folds pour la cross-validation
folds_train <- list()
folds_test <- list()
training_data <- list()
testing_data <- list()
training_xgb <- list()
testing_xgb <- list()
training_h2o <- list()
testing_h2o <- list()
combinations_train <- c(list(1, 1, 2, 2, 3, 3), combn(3, 1, simplify = FALSE), combn(3, 2, simplify = FALSE))
combinations_test <- c(list(2, 3, 1, 3, 1, 2), rev(combn(3, 2, simplify = FALSE)), rev(combn(3, 1, simplify = FALSE)))
temp_factors <- as.factor(group_path$path_ID)
# Création des données d'entrainement et de validation
for (i in 1:12) {
  
  # Création des folds d'entrainement et de validation
  folds_train[[i]] <- which(group_room[["dataset_ID"]] %in% combinations_train[[i]])
  folds_test[[i]] <- which(group_room[["dataset_ID"]] %in% combinations_test[[i]])
  
  # Recherche et suppression du label 3 lorsque la salle 1 est isolée (soit en train on enlève en test, soit en test on enlève en train)
  if ((length(combinations_train[[i]]) == 1) & (combinations_train[[i]][1] == 1)) {
    folds_test[[i]] <- folds_test[[i]][group_path$path_ID[folds_test[[i]]] != 3]
  }
  if ((length(combinations_test[[i]]) == 1) & (combinations_test[[i]][1] == 1)) {
    folds_train[[i]] <- folds_train[[i]][group_path$path_ID[folds_train[[i]]] != 3]
  }
  
  # Création des données d'entrainement et de validation
  training_data[[i]] <- mini_lm[folds_train[[i]], ]
  testing_data[[i]] <- mini_lm[folds_test[[i]], ]
  
  # Enregistrement des données CSV
  fwrite(training_data[[i]], paste0(file_tag, "trainNL_", sprintf("%02d", i), ".csv"))
  fwrite(testing_data[[i]], paste0(file_tag, "testNL_", sprintf("%02d", i), ".csv"))
  
  # Transformation des données au format approprié pour xgboost
  training_xgb[[i]] <- xgb.DMatrix(data = as.matrix(training_data[[i]]), label = group_path$path_ID[folds_train[[i]]] - 1)
  testing_xgb[[i]] <- xgb.DMatrix(data = as.matrix(testing_data[[i]]), label = group_path$path_ID[folds_test[[i]]] - 1)
  
  # Dumping des datasets binaires xgboost
  xgb.DMatrix.save(training_xgb[[i]], paste0(file_tag, "trainL_", sprintf("%02d", i), ".data"))
  xgb.DMatrix.save(testing_xgb[[i]], paste0(file_tag, "testL_", sprintf("%02d", i), ".data"))
  
  # Transformation des données au format approprié pour H2O
  training_h2o[[i]] <- as.h2o(cbind(Label = temp_factors[folds_train[[i]]], training_data[[i]]))
  testing_h2o[[i]] <- as.h2o(cbind(Label = temp_factors[folds_test[[i]]], testing_data[[i]]))
  
  # Enregistrement des frames H2O (CSV + Label)
  h2o.exportFile(training_h2o[[i]], paste0(file_tag, "trainL_", sprintf("%02d", i), ".csv"), force = TRUE)
  h2o.exportFile(testing_h2o[[i]], paste0(file_tag, "testL_", sprintf("%02d", i), ".csv"), force = TRUE)
  
}
# Temps nécessaire
timing(CurrentTime, "Préparation de l'évaluation du modèle nettoyé")
Temps d'exécution de la tâche 'Préparation de l'évaluation du modèle nettoyé' : 17.22 secondes.  

4.1 Nouvel entraînement des douze modèles

Nous pouvons relancer les calculs pour déterminer la performance des modèles. Un entrainement de tous les modèles est bien sûr nécessaire, ainsi que la validation de leurs performances sur des échantillons “inconnus”.

# Compteur de temps
CurrentTime <- timer() # Chunk Création et évaluation des douze modèles de benchmark nettoyé
# Où sauvegarder les fichiers ?
file_tag <- "2_models/"
file_h2o <- "2_models"
# Boucle d'évaluation
for (i in 1:12) {
  
  # Entrainement du modèle de régression logistique (xgboost)
  temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
                                  test = testing_xgb[[i]],
                                  booster = "gblinear", # Linéaire
                                  nrounds = 1000000, # Arrêté au meilleur résultat
                                  num_parallel_trees = 1)
  xgb.dump(model = temp_model, # Modèle à enregistrer
           fname = paste0(file_tag, "xgb_glm_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
           with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
           dump_format = "json") # Dump au format json, ré-utilisable
  accuracy[i, 2] <- 1 - temp_model$evaluation_log[[2]][temp_model$best_iteration] # Récupération du meilleur résultat
  
  # Entrainement du modèle d'arbre de décision (xgboost)
  temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
                                  test = testing_xgb[[i]],
                                  booster = "gbtree", # Non-linéaire
                                  nrounds = 1, # Un seul arbre
                                  num_parallel_trees = 1)
  xgb.dump(model = temp_model, # Modèle à enregistrer
           fname = paste0(file_tag, "xgb_dt_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
           with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
           dump_format = "json") # Dump au format json, ré-utilisable
  accuracy[i, 3] <- 1 - temp_model$evaluation_log[[2]][1] # Récupération du meilleur résultat
  
  # Entrainement du modèle de Random Forest (xgboost)
  temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
                                  test = testing_xgb[[i]],
                                  booster = "gbtree", # Non-linéaire
                                  nrounds = 1, # Une seule itération
                                  num_parallel_trees = 200) # De 200 arbres
  xgb.dump(model = temp_model, # Modèle à enregistrer
           fname = paste0(file_tag, "xgb_rf_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
           with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
           dump_format = "json") # Dump au format json, ré-utilisable
  accuracy[i, 4] <- 1 - temp_model$evaluation_log[[2]] # Récupération du meilleur résultat
  
  # Entrainement du modèle d'arbre de décision boosté avec protection contre l'overfitting (xgboost)
  temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
                                  test = testing_xgb[[i]],
                                  booster = "gbtree", # Non-linéaire
                                  nrounds = 1000000, # Arrêté au meilleur résultat
                                  num_parallel_trees = 1)
  xgb.dump(model = temp_model, # Modèle à enregistrer
           fname = paste0(file_tag, "xgb_gbt_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
           with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
           dump_format = "json") # Dump au format json, ré-utilisable
  accuracy[i, 5] <- 1 - temp_model$evaluation_log[[2]][temp_model$best_iteration] # Récupération du meilleur résultat
  
  # Entrainement du modèle de régression logistique (h2o)
  temp_model <- h2o.glm(y = 1,
                        training_frame = training_h2o[[i]],
                        validation_frame = testing_h2o[[i]],
                        model_id = paste0("h2o_glm_", sprintf("%02d", i)), # Nom du modèle
                        max_iterations = 100, # 100 itérations d'optimisation
                        solver = "IRLSM", # Solveur par défaut
                        standardize = FALSE, # Pas de standardisation puisque [-1, 1]
                        family = "multinomial", # Classification multi-classe
                        seed = 0, # Reproduction des résultats
                        intercept = TRUE)
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 6] <- temp_model@model$validation_metrics@metrics$hit_ratio_table[1, 2]
  
  # Entrainement du modèle d'arbre de décision (h2o)
  temp_model <- h2o.randomForest(y = 1,
                                 training_frame = training_h2o[[i]],
                                 validation_frame = testing_h2o[[i]],
                                 model_id = paste0("h2o_dt_", sprintf("%02d", i)), # Nom du modèle
                                 sample_rate = 1, # Toutes les observations seront prises en compte pour le seul arbre de décision
                                 mtries = 36, # Toutes les features seront prises en compte pour le seul arbre de décision
                                 ntrees = 1, # Un seul arbre
                                 seed = 0) # Reproduction des résultats
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 7] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
  # Entrainement du modèle de Random Forest (h2o)
  temp_model <- h2o.randomForest(y = 1,
                                 training_frame = training_h2o[[i]],
                                 validation_frame = testing_h2o[[i]],
                                 model_id = paste0("h2o_rf_", sprintf("%02d", i)), # Nom du modèle
                                 sample_rate = 0.632, # Bootstrapping .632 pour chaque arbre de décision
                                 mtries = -1, # sqrt(36) features seront prises en compte pour chaque arbre de décision
                                 ntrees = 200, # 200 arbres
                                 seed = 0) # Reproduction des résultats
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 8] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
  # Entrainement du modèle d'arbre de décision boosté avec protection contre l'overfitting (h2o)
  temp_model <- h2o.gbm(y = 1,
                      training_frame = training_h2o[[i]],
                      validation_frame = testing_h2o[[i]],
                      model_id = paste0("h2o_gbt_", sprintf("%02d", i)), # Nom du modèle
                      distribution = "multinomial", # Classification multi-classe
                      sample_rate = 1, # Pas de processus stochastique
                      ntrees = 100, # 100 itérations de boosting au maximum
                      score_each_iteration = TRUE, # Noter la valeur de chaque itération
                      stopping_rounds = 10, # Arrêt après 10 itérations sans amélioraton de la métrique
                      stopping_metric = "misclassification", # Surveiller l'inexactitude de la classification pour l'arrêt
                      stopping_tolerance = 0.00001, # Arrêter lorsque la métrique stagne de 0.001%
                      seed = 0) # Reproduction des résultats
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 9] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
  # Entrainement du réseau de neurones à architecture 32x6 + ReLU (h2o)
  temp_model <- h2o_nn_train(train = training_h2o[[i]],
                             test = testing_h2o[[i]],
                             model_id = paste0("h2o_nn_32x6_ReLU_", sprintf("%02d", i)), # Nom du modèle
                             activation = "Rectifier", # ReLU
                             hidden = 32) # Architecture 32x6
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 10] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
  # Entrainement du réseau de neurones à architecture 32x6 + Tanh (h2o)
  temp_model <- h2o_nn_train(train = training_h2o[[i]],
                             test = testing_h2o[[i]],
                             model_id = paste0("h2o_nn_32x6_Tanh_", sprintf("%02d", i)), # Nom du modèle
                             activation = "Tanh", # "Sigmoide"
                             hidden = 32) # Architecture 32x6
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 11] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
  # Entrainement du réseau de neurones à architecture 16x16x6 + ReLU (h2o)
  temp_model <- h2o_nn_train(train = training_h2o[[i]],
                             test = testing_h2o[[i]],
                             model_id = paste0("h2o_nn_16x16x6_ReLU_", sprintf("%02d", i)), # Nom du modèle
                             activation = "Rectifier", # ReLU
                             hidden = c(16, 16)) # Architecture 16x16x6
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 12] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
  # Entrainement du réseau de neurones à architecture 16x16x6 + Tanh (h2o)
  temp_model <- h2o_nn_train(train = training_h2o[[i]],
                             test = testing_h2o[[i]],
                             model_id = paste0("h2o_nn_16x16x6_Tanh_", sprintf("%02d", i)), # Nom du modèle
                             activation = "Tanh", # "Sigmoide"
                             hidden = c(16, 16)) # Architecture 16x16x6
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 13] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
}
# Temps nécessaire
timing(CurrentTime, "Création et évaluation des douze modèles de benchmark nettoyé")
Temps d'exécution de la tâche 'Création et évaluation des douze modèles de benchmark nettoyé' : 182.64 secondes.  

4.2 Nouvelle analyse des résultats

Nous voyons clairement que les modèles linéaires se démarquent de tous les autres modèles non-linéaires. Il y a eu une raison toute simple à a cela : les modèles non-linéaires utilisés ne peuvent s’accomoder face à de nouvelles valeurs de manière echelonnée, ce qui est tout le contraire des modèles linéaires !

# Moyenne des résultats
for (i in 2:13) {
  accuracy[13, i] <- mean(accuracy[1:6, i])
  accuracy[14, i] <- mean(accuracy[7:9, i])
  accuracy[15, i] <- mean(accuracy[10:12, i])
  accuracy[16, i] <- mean(accuracy[13:15, i])
}
# Enregistrement des scores
fwrite(accuracy, "scores/2_models.csv")
# Affichage des résultats dans un tableau interactif
to_print <- data.table(t(accuracy[13:16, -1])) # Préparation des données à mettre sur table
colnames(to_print) <- c("1 contre 1", "1 contre 2", "2 contre 1", "Moyenne") # Remise des noms des colonnes
row.names(to_print) <- colnames(accuracy)[-1] # Remise des noms des lignes
datatable(to_print,
          filter = "top", # Filtrage au-dessus de la table
          class = "cell-border stripe", # CSS
          extensions = c("ColReorder",
                         "RowReorder"), # Reordonner manuellement à la main
          options = list(pageLength = 12, # Page affichant 12 lignes
                         order = list(list(4, "desc")), # Ordonner par défaut par l'exactitude moyenne
                         colReorder = TRUE, # Plugin
                         rowReorder = TRUE)) %>% # Plugin
  formatStyle(c("1 contre 1", "1 contre 2", "2 contre 1"),
                  background = styleColorBar(c(0, 1), 'lightgreen'), # Couleur vert clair pour les métriques par fold
                  backgroundSize = '100% 90%',
                  backgroundRepeat = 'no-repeat',
                  backgroundPosition = 'center') %>%
  formatStyle("Moyenne",
              background = styleColorBar(c(0, 1), 'pink'), # Couleur rose pour la métrique de moyenne
              backgroundSize = '100% 90%',
              backgroundRepeat = 'no-repeat',
              backgroundPosition = 'center') %>%
  formatPercentage(columns = c("1 contre 1", "1 contre 2", "2 contre 1"),
              digits = 8) %>%
  formatPercentage(columns = "Moyenne",
              digits = 8)

# Affichage des résultats dans un tableau statique
formattable(accuracy[, c(1, 2:5)], list(formattable::area(col = xgb_LinearModel:xgb_GradientBoosting) ~ color_bar("orange")))

formattable(accuracy[, c(1, 6:9)], list(formattable::area(col = h2o_LinearModel:h2o_GradientBoosting) ~ color_bar("cyan")))

formattable(accuracy[, c(1, 10:13)], list(formattable::area(col = h2o_NN_32x6_ReLU:h2o_NN_16x16x6_Soft) ~ color_bar("yellow")))

4.3 Démonstration du problème linéaire

Il est travial de démontrer qu’une partie du problème ne peut être résolue de manière non-linéaire en l’état sans une analyse approfondie. Les résidus, par exemple, ont une valeur moyenne bien plus grande dans la salle 3 (plus difficile à prévoir) que les deux autres salles.

Par exemple, les résidus montrent que la salle 1 est à dissocier des deux autres salles. Cela est vérifiable en estimant la différence des features entre la salle 1 et les deux autres salles. Par sécurité, on utilisera le test U de Mann-Whitney à deux bornes, avec calcul exact (sans correction de continuité). Si la p-value est supérieure à 0.05 (si l’on suppose notre seuil de décision à 5% d’erreur), le test ne rejette pas l’hypothèse nulle (différence des médianes égale à zéro). Dans le cas contraire, la différence des médianes est statistiquement supérieure à 0 (les deux échantillons, l’une dans la salle 1, l’autre dans les deux autres salles, sont statistiquement différents). On affichera l’intervalle de confiance à 95% des médianes.

# Compteur de temps
CurrentTime <- timer() # Préparation de l'évaluation du modèle nettoyé
# Pré-initialisation des variables
temp_utest <- data.frame(Feature = c(paste0(rep(c(paste0("Coef", 1:4), paste0("Rési", 1:4)), 4), paste0("_", inverse.rle(list(lengths = rep(8, 4), values = 1:4)))), paste0("PosInitiale_", 1:4)),
                         p_value = numeric(36),
                         median_est = numeric(36),
                         median_inf95 = numeric(36),
                         median_sup95 = numeric(36))
# Mann-Whitney exact two-tailed U-test
for (i in 1:36) {
  temp_whitney <- wilcox.test(mini_lm[[i]][group_path$path_ID == 1], mini_lm[[i]][group_path$path_ID != 1], alternative = "two.sided", paired = FALSE, exact = TRUE, conf.int = TRUE, conf.level = 0.95)
  temp_utest[i, 2:5] <- c(temp_whitney$p.value, temp_whitney$estimate, temp_whitney$conf.int)
}
# Dump des données des tests U à deux bornes de Mann-Whitney
fwrite(accuracy, "stats/u_test.csv")
# Tableau interactif du U test de Mann-Whitney
datatable(temp_utest,
        filter = "top", # Filtrage au-dessus de la table
        class = "cell-border stripe", # CSS
        extensions = c("ColReorder",
                       "RowReorder"), # Reordonner manuellement à la main
        options = list(order = list(list(2, "desc")), # Ordonner par défaut par les facteurs ayant la p.value la plus grande
                       colReorder = TRUE, # Plugin
                       rowReorder = TRUE)) %>% # Plugin
  formatStyle("p_value",
                  background = styleColorBar(c(0, 1), 'lightblue'), # Couleur bleue pour le coefficient
                  backgroundSize = '100% 90%',
                  backgroundRepeat = 'no-repeat',
                  backgroundPosition = 'center') %>%
  formatStyle(c("median_est", "median_inf95", "median_sup95"),
              background = styleColorBar(range(temp_utest[, 3:5]), 'pink'), # Couleur rose pour la différence de médiane estimée
              backgroundSize = '100% 90%',
              backgroundRepeat = 'no-repeat',
              backgroundPosition = 'center') %>%
  formatRound(columns = c("p_value", "median_est", "median_inf95", "median_sup95"),
              digits = 6)

# Création de la table pour la visualisation
temp_lm <- copy(mini_lm)
colnames(temp_lm) <- c(paste0(rep(c(paste0("Coef", 1:4), paste0("Rési", 1:4)), 4), paste0("_", inverse.rle(list(lengths = rep(8, 4), values = 1:4)))), paste0("PosInitiale_", 1:4))
timing(CurrentTime, "Calculs statistiques")
Temps d'exécution de la tâche 'Calculs statistiques' : 777.92 secondes.  
# Tableplot des coefficients, trajectoires, et salles
plot(tableplot(dat = cbind(Trajectoire = as.factor(group_path$path_ID), Salle = as.factor(group_room$dataset_ID), temp_lm[, c(1:4, 9:12, 17:20, 25:28)]), sortCol = 2, nBins = 20, scales = "lin", plot = FALSE), title = "Trajectoire vs Coefficients")
Error in file.info(x) : invalid filename argument

# Tableplot des résidus, trajectoires, et salles
plot(tableplot(dat = cbind(Trajectoire = as.factor(group_path$path_ID), Salle = as.factor(group_room$dataset_ID), temp_lm[, c(5:8, 13:16, 21:24, 29:32)]), sortCol = 2, nBins = 20, scales = "lin", plot = FALSE), title = "Trajectoire vs Résidus")
Error in file.info(x) : invalid filename argument

# Tableplot des positions initiales, trajectoires, et salles
plot(tableplot(dat = cbind(Trajectoire = as.factor(group_path$path_ID), Salle = as.factor(group_room$dataset_ID), temp_lm[, c(33:36)]), sortCol = 2, nBins = 20, scales = "lin", plot = FALSE), title = "Trajectoire vs Position Initiale")
Error in file.info(x) : invalid filename argument

4.4 Analyse du modèle de régression logistique

Pour analyser le modèle de régression logistique, nous allons utiliser le modèle linéaire xgboost qui est un modèle très simple (équivalent d’une “régression linéaire avec une application sigmoïdale (Softmax)”). Il reste clair que la salle 1 donne toujours des problèmes, mais les résultats sont déjà bien meilleurs avec une exactitude d’environ 57%! (qui est nettement supérieure au modèle aléatoire, devant atteindre uniquement 25.00% de précision).

Pour éviter toute confusion, nous utiliserons la sémantique suivante : Fold = Salle. De plus, nous utiliserons les valeurs absolues afin de ne pas impacter les nombres vers zéros (s’ils paraissent à la fois négatifs et positifs, pour différents folds). Les signes sont donnés séparément.

La matrice de confusion nous montre claireemnt que les chemins 1 et 2 sont problématiques : ils se mélangent. De même pour la salle 3, dont l’exactitude de la prédiction n’est que de 12%.

# Compteur de temps
CurrentTime <- timer() # Chunk Préparation de l'analyse du modèle de régression logistique
# Pré-initialisation des variables
predictedValues <- matrix(nrow = 314, ncol = 6)
evolution <- list()
temp_dt <- list()
temp_means <- data.frame(Feature = c(paste0(rep(c(paste0("Coef", 1:4), paste0("Rési", 1:4)), 4), paste0("_", inverse.rle(list(lengths = rep(8, 4), values = 1:4)))), paste0("PosInitiale_", 1:4)),
                         Fold_1 = numeric(36),
                         Fold_2 = numeric(36),
                         Fold_3 = numeric(36),
                         Fold_Mean = numeric(36),
                         Feature_Mean = numeric(36),
                         Feature_SD = numeric(36))
# Boucle d'entrainement 2 contre 1
for (i in 10:12) {
  
  # Entrainement d'un modèle linéaire
  temp_model <- xgb.train(data = training_xgb[[i]],
                          num_class = 6, # Classification à 6 classes
                          nthread = 1, # 1 coeur utilisé
                          nrounds = 1000000, # Nombre d'itérations de boosting
                          eta = 0.10, # Shrinkage pour le boosting
                          booster = "gblinear", # Type d'entrainement : linéaire ou non-linéaire
                          objective = "multi:softprob", # Gradient/Hessian pour l'optimisation par Gradient Descent
                          eval_metric = "merror", # Inexactitude de la classification
                          maximize = FALSE, # Minimisation de l'erreur
                          early_stopping_rounds = 100, # Arrêt après 100 itérations sans amélioration de la métrique
                          verbose = FALSE, # Sans print des itérations
                          watchlist = list(test = testing_xgb[[i]]), # Estimation sur les données de test
                          callbacks = list(cb.evaluation.log())) # Logging des données d'entrainement pour pouvoir récupérer les métriques
  
  # Enregistrement du log
  evolution[[i - 9]] <- cbind(temp_model$evaluation_log, Fold = rep(13 - i, temp_model$niter))
  
  # Entrainement du meilleur modèle (obtention des meilleurs coefficients)
  temp_model <- xgb.train(data = training_xgb[[i]],
                          num_class = 6, # Classification à 6 classes
                          nthread = 1, # 1 coeur utilisé
                          nrounds = temp_model$best_iteration, # Nombre d'itérations de boosting
                          eta = 0.10, # Shrinkage pour le boosting
                          booster = "gblinear", # Type d'entrainement : linéaire ou non-linéaire
                          objective = "multi:softprob", # Gradient/Hessian pour l'optimisation par Gradient Descent
                          eval_metric = "merror", # Inexactitude de la classification
                          maximize = FALSE, # Minimisation de l'erreur
                          early_stopping_rounds = 99999, # Sans arrêt
                          verbose = FALSE, # Sans print des itérations
                          watchlist = list(test = testing_xgb[[i]]), # Estimation sur les données de test
                          callbacks = list(cb.evaluation.log())) # Logging des données d'entrainement pour pouvoir récupérer les métriques
  
  # Prédiction du modèle linéaire
  predictedValues[folds_test[[i]], ] <- t(matrix(predict(temp_model, testing_xgb[[i]], ntreelimit = 0), nrow = 6))
  
  # Calcul et formattage de l'importance des variables
  temp_importance <- data.table(Feature = temp_means[["Feature"]],
                                matrix(xgb.importance(model = temp_model)$Weight, ncol = 6))
  colnames(temp_importance) <- c("Feature", paste0("Label_", 1:6))
  temp_importance[["Sign"]] <- paste0(ifelse(temp_importance[[2]] >= 0, "+", "-"), ifelse(temp_importance[[3]] >= 0, "+", "-"), ifelse(temp_importance[[4]] >= 0, "+", "-"), ifelse(temp_importance[[5]] >= 0, "+", "-"), ifelse(temp_importance[[6]] >= 0, "+", "-"), ifelse(temp_importance[[7]] >= 0, "+", "-"))
  temp_importance[, 2:7] <- abs(temp_importance[, 2:7, with = FALSE]) 
  temp_means[[14 - i]] <- rowMeans(temp_importance[, 2:7, with = FALSE])
  temp_importance[[paste0("Fold_", 13 - i, "_Mean")]] <- temp_means[[14 - i]]
  
  # Enregistrement sous forme de tableau interactif
  temp_dt[[i - 9]] <- datatable(temp_importance,
        filter = "top", # Filtrage au-dessus de la table
        class = "cell-border stripe", # CSS
        extensions = c("ColReorder",
                       "RowReorder"), # Reordonner manuellement à la main
        options = list(order = list(list(9, "desc")), # Ordonner par défaut par les facteurs ayant le poids le plus gros
                       colReorder = TRUE, # Plugin
                       rowReorder = TRUE)) %>% # Plugin
  formatStyle(paste0("Label_", 1:6),
                  background = styleColorBar(range(temp_importance[, 2:7, with = FALSE]), 'lightblue'), # Couleur bleue pour le coefficient
                  backgroundSize = '100% 90%',
                  backgroundRepeat = 'no-repeat',
                  backgroundPosition = 'center') %>%
  formatStyle(paste0("Fold_", 13 - i, "_Mean"),
              background = styleColorBar(range(temp_importance[[9]]), 'pink'), # Couleur rose pour la métrique de moyenne
              backgroundSize = '100% 90%',
              backgroundRepeat = 'no-repeat',
              backgroundPosition = 'center') %>%
  formatRound(columns = c(paste0("Label_", 1:6), paste0("Fold_", 13 - i, "_Mean")),
              digits = 6)
  
}
# Calcul du poids moyen affecté à chaque feature
temp_means[[5]] <- rowMeans(temp_means[, 2:4]) # Poids moyen
temp_means[[6]] <- apply(mini_lm, 2, function(x) {mean(x)}) # Moyenne de la feature dans les données
temp_means[[7]] <- apply(mini_lm, 2, function(x) {sd(x)}) # Ecart-type de la feature dans les données
# Prépration du tableau interactif sur les poids moyens agrégés
temp_dt[[4]] <- datatable(temp_means,
        filter = "top", # Filtrage au-dessus de la table
        class = "cell-border stripe", # CSS
        extensions = c("ColReorder",
                       "RowReorder"), # Reordonner manuellement à la main
        options = list(order = list(list(5, "desc")), # Ordonner par défaut par les facteurs ayant le poids le plus gros en moyenne
                       colReorder = TRUE, # Plugin
                       rowReorder = TRUE)) %>% # Plugin
  formatStyle(paste0("Fold_", 1:3),
                  background = styleColorBar(range(temp_means[, 2:4]), 'lightblue'), # Couleur bleue pour le coefficient
                  backgroundSize = '100% 90%',
                  backgroundRepeat = 'no-repeat',
                  backgroundPosition = 'center') %>%
  formatStyle("Fold_Mean",
              background = styleColorBar(range(temp_means[[5]]), 'pink'), # Couleur rose pour la métrique de moyenne
              backgroundSize = '100% 90%',
              backgroundRepeat = 'no-repeat',
              backgroundPosition = 'center') %>%
  formatStyle("Feature_Mean",
              background = styleColorBar(range(temp_means[[6]]), 'lightgreen'), # Couleur verte pour la moyenne des features
              backgroundSize = '100% 90%',
              backgroundRepeat = 'no-repeat',
              backgroundPosition = 'center') %>%
  formatStyle("Feature_SD",
              background = styleColorBar(range(temp_means[[7]]), 'orange'), # Couleur verte pour l'écart-type des features
              backgroundSize = '100% 90%',
              backgroundRepeat = 'no-repeat',
              backgroundPosition = 'center') %>%
  formatRound(columns = c(paste0("Fold_", 1:3), "Fold_Mean", "Feature_Mean", "Feature_SD"),
              digits = 6)
# Dépivotage du log
evolution <- rbindlist(evolution)
colnames(evolution) <- c("Iteration", "Exactitude", "Fold")
evolution$Exactitude <- 1 - evolution$Exactitude
evolution$Fold <- as.factor(evolution$Fold)
# Prédiction à partir des probabilités
predictedLabel <- data.frame(Label = group_path$path_ID, Prediction = apply(predictedValues, 1, function(x) {which.max(x)}))
timing(CurrentTime, "Préparation de l'analyse du modèle de régression logistique")
Temps d'exécution de la tâche 'Préparation de l'analyse du modèle de régression logistique' : 5.54 secondes.  
# Affichage de l'évolution de la performance du modèle selon le nombre d'itération, sous forme de plot interactif
ggplotly(ggplot(data = evolution, aes_string(x = "Iteration", y = "Exactitude", group = "Fold", color = "Fold")) + geom_line() + geom_point() + scale_color_brewer(palette = "Set2") + theme_bw() + labs(title = "Evolution de l'exactitude par rapport au nombre d'itérations d'entrainement"), width = 960, height = 720)

# Affichage de la matrice de confusion sous forme de plot interactif
confusion_mat <- expand.grid(Label = 1:6, Prediction = 1:6)
confusion_mat <- merge(confusion_mat, data.table(predictedLabel)[, list(Freq = sum(.N)), by = list(Label, Prediction)], by = c("Label", "Prediction"), all.x = TRUE)
confusion_mat[["Freq"]][is.na(confusion_mat[["Freq"]])] <- 0
ggplotly(ggplot() + geom_rect(data = data.frame(cent = 1:6), size = 2, fill = NA, colour = "black", aes(xmin = cent - 0.5, xmax = cent + 0.5, ymin = cent - 0.5, ymax = cent + 0.5)) + geom_tile(data = confusion_mat, aes_string(x = "Label", y = "Prediction", fill = "Freq")) + geom_text(data = confusion_mat, aes_string(x = "Label", y = "Prediction", label = "Freq")) + scale_x_discrete(name = "Trajectoire Réelle") + scale_y_discrete(name = "Trajectoire Prédite") + scale_fill_gradientn(colours = rev(brewer.pal_extended(3, "PiYG"))) + labs(title = "Matrice de Confusion de la Trajectoire", fill = "Fréquence"), width = 960, height = 720)

# Affichage des tables à la fin car le formattage possède un bug inhérent lorsqu'on a plusieurs datatables (DT) dans le même chunk
# htmltools::tagList(temp_dt[[3]], temp_dt[[2]], temp_dt[[1]], temp_dt[[4]])

5 Troisième Analyse Systémique

Notre dernière analyse systémique porte sur l’utilisation des ancres qui sont censées être correctes : il faut inverser les ancres 1 et 3, et 2 et 4 de la salle 1.

5.1 Correction finale des ancres

Pour corriger les ancres, il suffit de réaliser cette opération sur la salle 1 :

  • Ancre 1 => Ancre 3
  • Ancre 2 => Ancre 4
  • Ancre 3 => Ancre 1
  • Ancre 4 => Ancre 2
# Compteur de temps
CurrentTime <- timer() # Chunk Correction des ancres
# Recopie des features dans le sens correct (ancre 1<=>3, ancre 2<=>4)
for (i in which(group_room$dataset_ID == 1)) {
  data_pre[[i]][[1]] <- data_pre_old[[i]][[3]]
  data_pre[[i]][[3]] <- data_pre_old[[i]][[1]]
  data_pre[[i]][[2]] <- data_pre_old[[i]][[4]]
  data_pre[[i]][[4]] <- data_pre_old[[i]][[2]]
}
# Enregistrement des frames corrigées et des anciennes frames, pour avoir un set solide, en respectant la nomenclature initiale MovementAAL_RSS_xxx.csv.
for (i in 1:314) {
  fwrite(data_pre[[i]], paste0("dataset_corrected/MovementAAL_RSS_", i, ".csv"))
}
# Temps nécessaire
timing(CurrentTime, "Correction des ancres")
Temps d'exécution de la tâche 'Correction des ancres' : 0.68 secondes.  

5.2 Création des features corrigées

Une fois que les ancres correspondant aux bonnes ancres dans la salle 1, on peut créer les features corrigées.

# Compteur de temps
CurrentTime <- timer() # Chunk Création des features finales
# Pré-initialisation de la frame
mini_lm <- data.frame(matrix(nrow = 314, ncol = 36))
# Boucle par série temporelle
for (i in 1:314) {
  
  # Boucle par ancre
  for (j in 1:4) {
    
    # Entrainement d'un modèle linéaire utilisant les autres ancres, avec l'interceptrice
    temp_model <- fastLmPure(X = cbind(as.matrix(data_pre[[i]][, (1:4)[-j], with = FALSE]), rep(1, nrow(data_pre[[i]]))), y = data_pre[[i]][[j]])
    
    # Enregistrement des coefficients et des résidus
    mini_lm[i, (j * 8 - 7):(j * 8)] <- c(temp_model$coefficients, temp_model$stderr)
    
  }
  
  # Ajout du dernier élément de la série temporelle (4 ancres)
  mini_lm[i, 33:36] <- data_pre[[i]][nrow(data_pre[[i]]), ]
  
}
# Enregistrement des données au format CSV
fwrite(cbind(mini_lm, Group = group_room[["dataset_ID"]], Label = group_path[["path_ID"]]), "features/features3.csv")
# Temps nécessaire
timing(CurrentTime, "Création des features finales")
Temps d'exécution de la tâche 'Création des features finales' : 6.23 secondes.  

5.3 Enregistrement des features

Une fois corrigées, on enregistre les features comme on a fait au début.

# Compteur de temps
CurrentTime <- timer() # Préparation de l'évaluation du modèle final
# Où sauvegarder les fichiers ?
file_tag <- "3_data/"
# Initialisation de la variable qui accueillera la précision
accuracy <- data.frame(matrix(nrow = 16, ncol = 13))
colnames(accuracy) <- c("Fold", "xgb_LinearModel", "xgb_DecisionTree", "xgb_RandomForest", "xgb_GradientBoosting", "h2o_LinearModel", "h2o_DecisionTree", "h2o_RandomForest", "h2o_GradientBoosting", "h2o_NN_32x6_ReLU", "h2o_NN_32x6_Soft", "h2o_NN_16x16x6_ReLU", "h2o_NN_16x16x6_Soft")
accuracy[, 1] <- c("Fold_1v2", "Fold_1v3", "Fold_2v1", "Fold_2v3", "Fold_3v1", "Fold_3v2", "Fold_1v23", "Fold_2v13", "Fold_3v12", "Fold_12v3", "Fold_13v2", "Fold_23v1", "Moyenne_1c1", "Moyenne_1c2", "Moyenne_2c1", "Moyenne")
# Initialisation des folds pour la cross-validation
folds_train <- list()
folds_test <- list()
training_data <- list()
testing_data <- list()
training_xgb <- list()
testing_xgb <- list()
training_h2o <- list()
testing_h2o <- list()
combinations_train <- c(list(1, 1, 2, 2, 3, 3), combn(3, 1, simplify = FALSE), combn(3, 2, simplify = FALSE))
combinations_test <- c(list(2, 3, 1, 3, 1, 2), rev(combn(3, 2, simplify = FALSE)), rev(combn(3, 1, simplify = FALSE)))
temp_factors <- as.factor(group_path$path_ID)
# Création des données d'entrainement et de validation
for (i in 1:12) {
  
  # Création des folds d'entrainement et de validation
  folds_train[[i]] <- which(group_room[["dataset_ID"]] %in% combinations_train[[i]])
  folds_test[[i]] <- which(group_room[["dataset_ID"]] %in% combinations_test[[i]])
  
  # Recherche et suppression du label 3 lorsque la salle 1 est isolée (soit en train on enlève en test, soit en test on enlève en train)
  if ((length(combinations_train[[i]]) == 1) & (combinations_train[[i]][1] == 1)) {
    folds_test[[i]] <- folds_test[[i]][group_path$path_ID[folds_test[[i]]] != 3]
  }
  if ((length(combinations_test[[i]]) == 1) & (combinations_test[[i]][1] == 1)) {
    folds_train[[i]] <- folds_train[[i]][group_path$path_ID[folds_train[[i]]] != 3]
  }
  
  # Création des données d'entrainement et de validation
  training_data[[i]] <- mini_lm[folds_train[[i]], ]
  testing_data[[i]] <- mini_lm[folds_test[[i]], ]
  
  # Enregistrement des données CSV
  fwrite(training_data[[i]], paste0(file_tag, "trainNL_", sprintf("%02d", i), ".csv"))
  fwrite(testing_data[[i]], paste0(file_tag, "testNL_", sprintf("%02d", i), ".csv"))
  
  # Transformation des données au format approprié pour xgboost
  training_xgb[[i]] <- xgb.DMatrix(data = as.matrix(training_data[[i]]), label = group_path$path_ID[folds_train[[i]]] - 1)
  testing_xgb[[i]] <- xgb.DMatrix(data = as.matrix(testing_data[[i]]), label = group_path$path_ID[folds_test[[i]]] - 1)
  
  # Dumping des datasets binaires xgboost
  xgb.DMatrix.save(training_xgb[[i]], paste0(file_tag, "trainL_", sprintf("%02d", i), ".data"))
  xgb.DMatrix.save(testing_xgb[[i]], paste0(file_tag, "testL_", sprintf("%02d", i), ".data"))
  
  # Transformation des données au format approprié pour H2O
  training_h2o[[i]] <- as.h2o(cbind(Label = temp_factors[folds_train[[i]]], training_data[[i]]))
  testing_h2o[[i]] <- as.h2o(cbind(Label = temp_factors[folds_test[[i]]], testing_data[[i]]))
  
  # Enregistrement des frames H2O (CSV + Label)
  h2o.exportFile(training_h2o[[i]], paste0(file_tag, "trainL_", sprintf("%02d", i), ".csv"), force = TRUE)
  h2o.exportFile(testing_h2o[[i]], paste0(file_tag, "testL_", sprintf("%02d", i), ".csv"), force = TRUE)
  
}
# Temps nécessaire
timing(CurrentTime, "Préparation de l'évaluation du modèle final")
Temps d'exécution de la tâche 'Préparation de l'évaluation du modèle final' : 18.95 secondes.  

5.4 Entrainement des douze modèles

On peut maintenant tester les douze modèles de manière fiable.

# Compteur de temps
CurrentTime <- timer() # Chunk Création et évaluation des douze modèles de benchmark final
# Où sauvegarder les fichiers ?
file_tag <- "3_models/"
file_h2o <- "3_models"
# Boucle d'évaluation
for (i in 1:12) {
  
  # Entrainement du modèle de régression logistique (xgboost)
  temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
                                  test = testing_xgb[[i]],
                                  booster = "gblinear", # Linéaire
                                  nrounds = 1000000, # Arrêté au meilleur résultat
                                  num_parallel_trees = 1)
  xgb.dump(model = temp_model, # Modèle à enregistrer
           fname = paste0(file_tag, "xgb_glm_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
           with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
           dump_format = "json") # Dump au format json, ré-utilisable
  accuracy[i, 2] <- 1 - temp_model$evaluation_log[[2]][temp_model$best_iteration] # Récupération du meilleur résultat
  
  # Entrainement du modèle d'arbre de décision (xgboost)
  temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
                                  test = testing_xgb[[i]],
                                  booster = "gbtree", # Non-linéaire
                                  nrounds = 1, # Un seul arbre
                                  num_parallel_trees = 1)
  xgb.dump(model = temp_model, # Modèle à enregistrer
           fname = paste0(file_tag, "xgb_dt_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
           with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
           dump_format = "json") # Dump au format json, ré-utilisable
  accuracy[i, 3] <- 1 - temp_model$evaluation_log[[2]][1] # Récupération du meilleur résultat
  
  # Entrainement du modèle de Random Forest (xgboost)
  temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
                                  test = testing_xgb[[i]],
                                  booster = "gbtree", # Non-linéaire
                                  nrounds = 1, # Une seule itération
                                  num_parallel_trees = 200) # De 200 arbres
  xgb.dump(model = temp_model, # Modèle à enregistrer
           fname = paste0(file_tag, "xgb_rf_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
           with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
           dump_format = "json") # Dump au format json, ré-utilisable
  accuracy[i, 4] <- 1 - temp_model$evaluation_log[[2]] # Récupération du meilleur résultat
  
  # Entrainement du modèle d'arbre de décision boosté avec protection contre l'overfitting (xgboost)
  temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
                                  test = testing_xgb[[i]],
                                  booster = "gbtree", # Non-linéaire
                                  nrounds = 1000000, # Arrêté au meilleur résultat
                                  num_parallel_trees = 1)
  xgb.dump(model = temp_model, # Modèle à enregistrer
           fname = paste0(file_tag, "xgb_gbt_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
           with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
           dump_format = "json") # Dump au format json, ré-utilisable
  accuracy[i, 5] <- 1 - temp_model$evaluation_log[[2]][temp_model$best_iteration] # Récupération du meilleur résultat
  
  # Entrainement du modèle de régression logistique (h2o)
  temp_model <- h2o.glm(y = 1,
                        training_frame = training_h2o[[i]],
                        validation_frame = testing_h2o[[i]],
                        model_id = paste0("h2o_glm_", sprintf("%02d", i)), # Nom du modèle
                        max_iterations = 100, # 100 itérations d'optimisation
                        solver = "IRLSM", # Solveur par défaut
                        standardize = FALSE, # Pas de standardisation puisque [-1, 1]
                        family = "multinomial", # Classification multi-classe
                        seed = 0, # Reproduction des résultats
                        intercept = TRUE)
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 6] <- temp_model@model$validation_metrics@metrics$hit_ratio_table[1, 2]
  
  # Entrainement du modèle d'arbre de décision (h2o)
  temp_model <- h2o.randomForest(y = 1,
                                 training_frame = training_h2o[[i]],
                                 validation_frame = testing_h2o[[i]],
                                 model_id = paste0("h2o_dt_", sprintf("%02d", i)), # Nom du modèle
                                 sample_rate = 1, # Toutes les observations seront prises en compte pour le seul arbre de décision
                                 mtries = 36, # Toutes les features seront prises en compte pour le seul arbre de décision
                                 ntrees = 1, # Un seul arbre
                                 seed = 0) # Reproduction des résultats
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 7] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
  # Entrainement du modèle de Random Forest (h2o)
  temp_model <- h2o.randomForest(y = 1,
                                 training_frame = training_h2o[[i]],
                                 validation_frame = testing_h2o[[i]],
                                 model_id = paste0("h2o_rf_", sprintf("%02d", i)), # Nom du modèle
                                 sample_rate = 0.632, # Bootstrapping .632 pour chaque arbre de décision
                                 mtries = -1, # sqrt(36) features seront prises en compte pour chaque arbre de décision
                                 ntrees = 200, # 200 arbres
                                 seed = 0) # Reproduction des résultats
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 8] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
  # Entrainement du modèle d'arbre de décision boosté avec protection contre l'overfitting (h2o)
  temp_model <- h2o.gbm(y = 1,
                      training_frame = training_h2o[[i]],
                      validation_frame = testing_h2o[[i]],
                      model_id = paste0("h2o_gbt_", sprintf("%02d", i)), # Nom du modèle
                      distribution = "multinomial", # Classification multi-classe
                      sample_rate = 1, # Pas de processus stochastique
                      ntrees = 100, # 100 itérations de boosting au maximum
                      score_each_iteration = TRUE, # Noter la valeur de chaque itération
                      stopping_rounds = 10, # Arrêt après 10 itérations sans amélioraton de la métrique
                      stopping_metric = "misclassification", # Surveiller l'inexactitude de la classification pour l'arrêt
                      stopping_tolerance = 0.00001, # Arrêter lorsque la métrique stagne de 0.001%
                      seed = 0) # Reproduction des résultats
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 9] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
  # Entrainement du réseau de neurones à architecture 32x6 + ReLU (h2o)
  temp_model <- h2o_nn_train(train = training_h2o[[i]],
                             test = testing_h2o[[i]],
                             model_id = paste0("h2o_nn_32x6_ReLU_", sprintf("%02d", i)), # Nom du modèle
                             activation = "Rectifier", # ReLU
                             hidden = 32) # Architecture 32x6
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 10] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
  # Entrainement du réseau de neurones à architecture 32x6 + Tanh (h2o)
  temp_model <- h2o_nn_train(train = training_h2o[[i]],
                             test = testing_h2o[[i]],
                             model_id = paste0("h2o_nn_32x6_Tanh_", sprintf("%02d", i)), # Nom du modèle
                             activation = "Tanh", # "Sigmoide"
                             hidden = 32) # Architecture 32x6
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 11] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
  # Entrainement du réseau de neurones à architecture 16x16x6 + ReLU (h2o)
  temp_model <- h2o_nn_train(train = training_h2o[[i]],
                             test = testing_h2o[[i]],
                             model_id = paste0("h2o_nn_16x16x6_ReLU_", sprintf("%02d", i)), # Nom du modèle
                             activation = "Rectifier", # ReLU
                             hidden = c(16, 16)) # Architecture 16x16x6
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 12] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
  # Entrainement du réseau de neurones à architecture 16x16x6 + Tanh (h2o)
  temp_model <- h2o_nn_train(train = training_h2o[[i]],
                             test = testing_h2o[[i]],
                             model_id = paste0("h2o_nn_16x16x6_Tanh_", sprintf("%02d", i)), # Nom du modèle
                             activation = "Tanh", # "Sigmoide"
                             hidden = c(16, 16)) # Architecture 16x16x6
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 13] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
}
# Temps nécessaire
timing(CurrentTime, "Création et évaluation des douze modèles de benchmark final")
Temps d'exécution de la tâche 'Création et évaluation des douze modèles de benchmark final' : 183.56 secondes.  

5.5 Affichage des résultats

Les résultats de la performance des modèles entrainés sont ci-dessous. Les performances semblent légèrement en hausse (de 2% environ). En revanche, les performances sont plus faibles sur lorsque les données sont validées sur la salle 1.

# Moyenne des résultats
for (i in 2:13) {
  accuracy[13, i] <- mean(accuracy[1:6, i])
  accuracy[14, i] <- mean(accuracy[7:9, i])
  accuracy[15, i] <- mean(accuracy[10:12, i])
  accuracy[16, i] <- mean(accuracy[13:15, i])
}
# Enregistrement des scores
fwrite(accuracy, "scores/3_models.csv")
# Affichage des résultats dans un tableau interactif
to_print <- data.table(t(accuracy[13:16, -1])) # Préparation des données à mettre sur table
colnames(to_print) <- c("1 contre 1", "1 contre 2", "2 contre 1", "Moyenne") # Remise des noms des colonnes
row.names(to_print) <- colnames(accuracy)[-1] # Remise des noms des lignes
datatable(to_print,
          filter = "top", # Filtrage au-dessus de la table
          class = "cell-border stripe", # CSS
          extensions = c("ColReorder",
                         "RowReorder"), # Reordonner manuellement à la main
          options = list(pageLength = 12, # Page affichant 12 lignes
                         order = list(list(4, "desc")), # Ordonner par défaut par l'exactitude moyenne
                         colReorder = TRUE, # Plugin
                         rowReorder = TRUE)) %>% # Plugin
  formatStyle(c("1 contre 1", "1 contre 2", "2 contre 1"),
                  background = styleColorBar(c(0, 1), 'lightgreen'), # Couleur vert clair pour les métriques par fold
                  backgroundSize = '100% 90%',
                  backgroundRepeat = 'no-repeat',
                  backgroundPosition = 'center') %>%
  formatStyle("Moyenne",
              background = styleColorBar(c(0, 1), 'pink'), # Couleur rose pour la métrique de moyenne
              backgroundSize = '100% 90%',
              backgroundRepeat = 'no-repeat',
              backgroundPosition = 'center') %>%
  formatPercentage(columns = c("1 contre 1", "1 contre 2", "2 contre 1"),
              digits = 8) %>%
  formatPercentage(columns = "Moyenne",
              digits = 8)

# Affichage des résultats dans un tableau statique
formattable(accuracy[, c(1, 2:5)], list(formattable::area(col = xgb_LinearModel:xgb_GradientBoosting) ~ color_bar("orange")))

formattable(accuracy[, c(1, 6:9)], list(formattable::area(col = h2o_LinearModel:h2o_GradientBoosting) ~ color_bar("cyan")))

formattable(accuracy[, c(1, 10:13)], list(formattable::area(col = h2o_NN_32x6_ReLU:h2o_NN_16x16x6_Soft) ~ color_bar("yellow")))

5.6 Analyse du modèle de régression logistique

Les coefficients affectés selon les échantillons d’entrainement semblent moins stables qu’auparavant, peut être un problème d’échelle (ou de VIF entre les variables).

On remarque que le chemin 2 est bien mieux classé qu’auparavant, mais que la salle 3 est toujours difficile à classer (on est passé de 12% d’exactitude à 24%, ce qui est deux fois plus précis).

En revanche, la salle 4 semble souffir (40% d’exactitude), ce qui n’étais pas le cas avec l’approximation des signaux des ancres (55% d’exactitude).

# Compteur de temps
CurrentTime <- timer() # Chunk Préparation de l'analyse du modèle de régression logistique final
# Pré-initialisation des variables
predictedValues <- matrix(nrow = 314, ncol = 6)
evolution <- list()
temp_dt <- list()
temp_means <- data.frame(Feature = c(paste0(rep(c(paste0("Coef", 1:4), paste0("Rési", 1:4)), 4), paste0("_", inverse.rle(list(lengths = rep(8, 4), values = 1:4)))), paste0("PosInitiale_", 1:4)),
                         Fold_1 = numeric(36),
                         Fold_2 = numeric(36),
                         Fold_3 = numeric(36),
                         Fold_Mean = numeric(36),
                         Feature_Mean = numeric(36),
                         Feature_SD = numeric(36))
# Boucle d'entrainement 2 contre 1
for (i in 10:12) {
  
  # Entrainement d'un modèle linéaire
  temp_model <- xgb.train(data = training_xgb[[i]],
                          num_class = 6, # Classification à 6 classes
                          nthread = 1, # 1 coeur utilisé
                          nrounds = 1000000, # Nombre d'itérations de boosting
                          eta = 0.10, # Shrinkage pour le boosting
                          booster = "gblinear", # Type d'entrainement : linéaire ou non-linéaire
                          objective = "multi:softprob", # Gradient/Hessian pour l'optimisation par Gradient Descent
                          eval_metric = "merror", # Inexactitude de la classification
                          maximize = FALSE, # Minimisation de l'erreur
                          early_stopping_rounds = 100, # Arrêt après 100 itérations sans amélioration de la métrique
                          verbose = FALSE, # Sans print des itérations
                          watchlist = list(test = testing_xgb[[i]]), # Estimation sur les données de test
                          callbacks = list(cb.evaluation.log())) # Logging des données d'entrainement pour pouvoir récupérer les métriques
  
  # Enregistrement du log
  evolution[[i - 9]] <- cbind(temp_model$evaluation_log, Fold = rep(13 - i, temp_model$niter))
  
  # Entrainement du meilleur modèle (obtention des meilleurs coefficients)
  temp_model <- xgb.train(data = training_xgb[[i]],
                          num_class = 6, # Classification à 6 classes
                          nthread = 1, # 1 coeur utilisé
                          nrounds = temp_model$best_iteration, # Nombre d'itérations de boosting
                          eta = 0.10, # Shrinkage pour le boosting
                          booster = "gblinear", # Type d'entrainement : linéaire ou non-linéaire
                          objective = "multi:softprob", # Gradient/Hessian pour l'optimisation par Gradient Descent
                          eval_metric = "merror", # Inexactitude de la classification
                          maximize = FALSE, # Minimisation de l'erreur
                          early_stopping_rounds = 99999, # Sans arrêt
                          verbose = FALSE, # Sans print des itérations
                          watchlist = list(test = testing_xgb[[i]]), # Estimation sur les données de test
                          callbacks = list(cb.evaluation.log())) # Logging des données d'entrainement pour pouvoir récupérer les métriques
  
  # Prédiction du modèle linéaire
  predictedValues[folds_test[[i]], ] <- t(matrix(predict(temp_model, testing_xgb[[i]], ntreelimit = 0), nrow = 6))
  
  # Calcul et formattage de l'importance des variables
  temp_importance <- data.table(Feature = temp_means[["Feature"]],
                                matrix(xgb.importance(model = temp_model)$Weight, ncol = 6))
  colnames(temp_importance) <- c("Feature", paste0("Label_", 1:6))
  temp_importance[["Sign"]] <- paste0(ifelse(temp_importance[[2]] >= 0, "+", "-"), ifelse(temp_importance[[3]] >= 0, "+", "-"), ifelse(temp_importance[[4]] >= 0, "+", "-"), ifelse(temp_importance[[5]] >= 0, "+", "-"), ifelse(temp_importance[[6]] >= 0, "+", "-"), ifelse(temp_importance[[7]] >= 0, "+", "-"))
  temp_importance[, 2:7] <- abs(temp_importance[, 2:7, with = FALSE])
  temp_means[[14 - i]] <- rowMeans(temp_importance[, 2:7, with = FALSE])
  temp_importance[[paste0("Fold_", 13 - i, "_Mean")]] <- temp_means[[14 - i]]
  
  # Enregistrement sous forme de tableau interactif
  temp_dt[[i - 9]] <- datatable(temp_importance,
        filter = "top", # Filtrage au-dessus de la table
        class = "cell-border stripe", # CSS
        extensions = c("ColReorder",
                       "RowReorder"), # Reordonner manuellement à la main
        options = list(order = list(list(9, "desc")), # Ordonner par défaut par les facteurs ayant le poids le plus gros
                       colReorder = TRUE, # Plugin
                       rowReorder = TRUE)) %>% # Plugin
  formatStyle(paste0("Label_", 1:6),
                  background = styleColorBar(range(temp_importance[, 2:7, with = FALSE]), 'lightblue'), # Couleur bleue pour le coefficient
                  backgroundSize = '100% 90%',
                  backgroundRepeat = 'no-repeat',
                  backgroundPosition = 'center') %>%
  formatStyle(paste0("Fold_", 13 - i, "_Mean"),
              background = styleColorBar(range(temp_importance[[9]]), 'pink'), # Couleur rose pour la métrique de moyenne
              backgroundSize = '100% 90%',
              backgroundRepeat = 'no-repeat',
              backgroundPosition = 'center') %>%
  formatRound(columns = c(paste0("Label_", 1:6), paste0("Fold_", 13 - i, "_Mean")),
              digits = 6)
  
}
# Calcul du poids moyen affecté à chaque feature
temp_means[[5]] <- rowMeans(temp_means[, 2:4]) # Poids moyen
temp_means[[6]] <- apply(mini_lm, 2, function(x) {mean(x)}) # Moyenne de la feature dans les données
temp_means[[7]] <- apply(mini_lm, 2, function(x) {sd(x)}) # Ecart-type de la feature dans les données
# Prépration du tableau interactif sur les poids moyens agrégés
temp_dt[[4]] <- datatable(temp_means,
        filter = "top", # Filtrage au-dessus de la table
        class = "cell-border stripe", # CSS
        extensions = c("ColReorder",
                       "RowReorder"), # Reordonner manuellement à la main
        options = list(order = list(list(5, "desc")), # Ordonner par défaut par les facteurs ayant le poids le plus gros en moyenne
                       colReorder = TRUE, # Plugin
                       rowReorder = TRUE)) %>% # Plugin
  formatStyle(paste0("Fold_", 1:3),
                  background = styleColorBar(range(temp_means[, 2:4]), 'lightblue'), # Couleur bleue pour le coefficient
                  backgroundSize = '100% 90%',
                  backgroundRepeat = 'no-repeat',
                  backgroundPosition = 'center') %>%
  formatStyle("Fold_Mean",
              background = styleColorBar(range(temp_means[[5]]), 'pink'), # Couleur rose pour la métrique de moyenne
              backgroundSize = '100% 90%',
              backgroundRepeat = 'no-repeat',
              backgroundPosition = 'center') %>%
  formatStyle("Feature_Mean",
              background = styleColorBar(range(temp_means[[6]]), 'lightgreen'), # Couleur verte pour la moyenne des features
              backgroundSize = '100% 90%',
              backgroundRepeat = 'no-repeat',
              backgroundPosition = 'center') %>%
  formatStyle("Feature_SD",
              background = styleColorBar(range(temp_means[[7]]), 'orange'), # Couleur verte pour l'écart-type des features
              backgroundSize = '100% 90%',
              backgroundRepeat = 'no-repeat',
              backgroundPosition = 'center') %>%
  formatRound(columns = c(paste0("Fold_", 1:3), "Fold_Mean", "Feature_Mean", "Feature_SD"),
              digits = 6)
# Dépivotage du log
evolution <- rbindlist(evolution)
colnames(evolution) <- c("Iteration", "Exactitude", "Fold")
evolution$Exactitude <- 1 - evolution$Exactitude
evolution$Fold <- as.factor(evolution$Fold)
# Prédiction à partir des probabilités
predictedLabel <- data.frame(Label = group_path$path_ID, Prediction = apply(predictedValues, 1, function(x) {which.max(x)}))
timing(CurrentTime, "Préparation de l'analyse du modèle de régression logistique final")
Temps d'exécution de la tâche 'Préparation de l'analyse du modèle de régression logistique final' : 6.66 secondes.  
# Affichage de l'évolution de la performance du modèle selon le nombre d'itération, sous forme de plot interactif
ggplotly(ggplot(data = evolution, aes_string(x = "Iteration", y = "Exactitude", group = "Fold", color = "Fold")) + geom_line() + geom_point() + scale_color_brewer(palette = "Set2") + theme_bw() + labs(title = "Evolution de l'exactitude par rapport au nombre d'itérations d'entrainement"), width = 960, height = 720)

# Affichage de la matrice de confusion sous forme de plot interactif
confusion_mat <- expand.grid(Label = 1:6, Prediction = 1:6)
confusion_mat <- merge(confusion_mat, data.table(predictedLabel)[, list(Freq = sum(.N)), by = list(Label, Prediction)], by = c("Label", "Prediction"), all.x = TRUE)
confusion_mat[["Freq"]][is.na(confusion_mat[["Freq"]])] <- 0
ggplotly(ggplot() + geom_rect(data = data.frame(cent = 1:6), size = 2, fill = NA, colour = "black", aes(xmin = cent - 0.5, xmax = cent + 0.5, ymin = cent - 0.5, ymax = cent + 0.5)) + geom_tile(data = confusion_mat, aes_string(x = "Label", y = "Prediction", fill = "Freq")) + geom_text(data = confusion_mat, aes_string(x = "Label", y = "Prediction", label = "Freq")) + scale_x_discrete(name = "Trajectoire Réelle") + scale_y_discrete(name = "Trajectoire Prédite") + scale_fill_gradientn(colours = rev(brewer.pal_extended(3, "PiYG"))) + labs(title = "Matrice de Confusion de la Trajectoire", fill = "Fréquence"), width = 960, height = 720)

# Affichage des tables à la fin car le formattage possède un bug inhérent lorsqu'on a plusieurs datatables (DT) dans le même chunk
# htmltools::tagList(temp_dt[[3]], temp_dt[[2]], temp_dt[[1]], temp_dt[[4]])

6 Optimisation de la régression logistique

Il est tout à fait possible d’optimiser la régression logistique, par trois chemins :

  • Optimiser les hyperparamètres
  • Sélectionner le meilleur subset de features à utiliser
  • Utiliser de meilleures features

Par manque de temps, nous ne travaillerons pas sur l’élaboration de meilleures features. A la place, nous optimiserons les hyperparamètres et les features sélectionnées.

6.1 Optimisation par entropie croisée

Nous allons utiliser l’optimisation par entropie croisée (Cross-Entropy Optimization), qui donne des résultats remarquables dans la quasi intégralité des cas (à moins que toutes les features et tous les hyperparamètres soient déjà l’un des meilleurs possibles). Grâce à cette méthode, nous pouvons :

  • Optimiser les hyperparamètres de nos choix, qu’ils soient continus ou discrets
  • Sélectionner des features (discrétisation binaire pour la sélection)

Ici, nous avons trois hyperparamètres et 36 features à sélectionner :

  • alpha : régularisation L1 sur les coefficients, qu’on va constraindre entre 0 et 5
  • lambda : régularisation L2 sur les coefficients, qu’on va constraindre entre 0 et 5
  • lambda_bias : régularisation L2 sur le biais, qu’on va constraindre entre 0 et 5
  • 36 features : on souhaite réduire de 72% environ le nombre de features pour que le modèle final soit performant et simple à comprendre (environ 10 features)

Pour ne pas que l’optimiseur converge trop rapidement, nous allons utiliser ces paramètres d’optimisation :

  • optimisation du boosting : nous allons utiliser la perte logarithmique et non pas l’inexactitude pour interrompre le boosting, afin d’éviter un effet de chance et faire converger plus rapidement chaque modèle
  • valeur à optimiser : minimisation de la perte logarithmique de classification (logloss) et non pas de l’inexactitude de classification afin d’éviter l’overfitting involontaire ou l’effet de chance due à la faible quantité d’observations (mais également pour fournir une échelle continue et pas discrète à l’optimiseur pour la perte) - pour tenter d’optimiser l’exactitude, on multiplie l’inexactitude par la perte logarithmique qui sera passée comme valeur à optimiser par entropie croisée
  • échantillons par itérations : 250 modèles pour une population stable et diverse
  • elites : 10% d’elites pour avoir une population plutôt stable et diverse
  • nombre d’itérations : 20 pour laisser à l’optimiseur le temps de chercher (mais très rapidement), avec arrêt prématuré en cas de stagnation de l’optimisation pendant 5 itérations
  • optimisation des valeurs continues : convergence lorsque les hyperparamètres ont chacun un écart-type en-dessous de 0.10 dans la population élite
  • optimisation des valeurs discrètes : convergence lorsque la sélection des features est identique dans la population élite

Toutes les variables optimisées seront loggées et leurs évolutions seront visibles en temps réel via divers logiciels (exemple : BareTail), avec le tag “***" lorsque l’optimisation donne un nouveau minimum local.

Une recherche exhaustive ne fonctionnera pas même avec un petit subset de features recherchées : nous sommes en présence d’hyperparamètres continus et non discrets.

A la fin, nous avons 13 features avec une meilleure performance que celle du modèle initiale (jusqu’à 72% d’exactitude contre 65% initialement). Notre modèle nécessite donc plus de features qu’on souhaitait (36% des features au lieu de 28%), mais le gain en performance est majeur (+7%). On peut analyser le log pour extraire la meilleure combinaison qui nous permet de conserver uniquement des 10 features, mais on ne le fera pas par manque de temps ici.

# Compteur de temps
CurrentTime <- timer() # Optimisation par entropie croisée
CE_Features <- function(x, y, train, test) {
  
  # Pré-initialisation de certaines variables
  to_keep <- as.boolean(y)
  iters <<- iters + 1
  
  # Au moins une feature ?
  if (sum(to_keep) > 0) {
    
    error <- numeric(3)
    lloss <- numeric(3)
    
    for (i in 10:12) {
      
      temp_model <- xgb.train(data = xgb.DMatrix(data = as.matrix(train[[i]][, which(to_keep)]), label = train[[i]][["Label"]]), # Données d'entrainement
                              watchlist = list(test = xgb.DMatrix(data = as.matrix(test[[i]][, which(to_keep)]), label = test[[i]][["Label"]])), # Données de validation
                              num_class = 6, # 6 classe de classification
                              nthread = 1, # 1 thread pour la reproduction des résultats
                              nrounds = 1000, # 1000 itérations, où arrêt prématuré
                              alpha = x[1], # Régularisation L1 (Lasso)
                              lambda = x[2], # Régularisation L2 (Ridge)
                              lambda_bias = x[3], # Régularisation L2 du biais (Ridge)
                              eta = 0.10, # Shrinkage pour le boosting
                              booster = "gblinear", # Type d'entrainement : linéaire ou non-linéaire
                              objective = "multi:softprob", # Gradient/Hessian pour l'optimisation par Gradient Descent
                              eval_metric = "mlogloss", # Perte logiarthmique de la classification (cette métrique est optimisée par xgboost)
                              eval_metric = "merror", # Inexactitude de la classification
                              maximize = FALSE, # Minimisation de l'erreur
                              early_stopping_rounds = 50, # Arrêt après 50 itérations sans amélioration de la métrique
                              verbose = FALSE, # Sans print des itérations
                              callbacks = list(cb.evaluation.log())) # Logging des données d'entrainement pour pouvoir récupérer les métriques)
      error[13 - i] <- temp_model$evaluation_log$test_merror[temp_model$best_iteration] # Enregistrement du meilleur score (inexactitude)
      lloss[13 - i] <- temp_model$evaluation_log$test_mlogloss[temp_model$best_iteration] # Enregistrement du meilleur score (perte logarithmique)
      
    }
    
    # Enregistrement de l'erreur et logging dans l'environnemnt global
    error_list[iters, 2:4] <<- error # Inexactitude
    error <- mean(error) # Inexactitude moyenne
    error_list[iters, 5] <<- error # Inexactitude moyenne
    error_list[iters, 6:8] <<- lloss # Perte logarithmique
    lloss <- mean(lloss) # Perte logarithmique moyenne
    error_list[iters, 9] <<- lloss # Perte logarithmique moyenne
    score <- error * lloss # Score de perte
    error_list[iters, 10] <<- score # Score de perte
    error_list[iters, 11] <<- sum(to_keep) # Compte de features
    error_list[iters, 12:14] <<- as.numeric(x) # Hyperparamètres
    error_list[iters, 15:50] <<- as.numeric(to_keep) # Features utilisées
    error_list[iters, 51] <<- 1
    
    # Le résultat est-il meilleur ? (pour le logging en temps réel)
    if (error < best_error) {
      best_error <<- error
      star <- "(*** - "
    } else {
      star <- "(    - "
    }
    if (lloss < best_lloss) {
      best_lloss <<- lloss
      star <- paste0(star, "*** - ")
    } else {
      star <- paste0(star, "    - ")
    }
    if (score < best_score) {
      best_score <<- score
      star <- paste0(star, "***) ")
    } else {
      star <- paste0(star, "   ) ")
    }
    
    # Logging en temps réel
    cat(star, "[", format(Sys.time(), "%X"), "] Pass ", sprintf("%05d", iters), ": Error=", sprintf("%.05f", error), " - Loss=", sprintf("%.07f", lloss), " - Score=", sprintf("%.07f", score), " - feats=", sprintf("%04d", sum(to_keep)), " - alpha=", sprintf("%07.05f", x[1]), ", lambda=", sprintf("%07.05f", x[2]), ", lambda_bias=", sprintf("%07.05f", x[3]), "\n", sep = "", file = "optim/log.txt", append = TRUE)
    return(score)
    
  } else {
    
    # Logging en temps réel
    cat("(    -     -    ) [", format(Sys.time(), "%X"), "] Pass ", sprintf("%05d", iters), ": failed\n", sep = "", file = "optim/log.txt", append = TRUE)
    return(9.9999)
    
  }
  
}
# Où sauvegarder les fichiers ?
file_tag <- "4_data/"
# Création des données d'entrainement et de validation
for (i in 1:12) {
  
  # Création des données d'entrainement et de validation
  training_data[[i]][["Label"]] <- group_path$path_ID[folds_train[[i]]] - 1
  testing_data[[i]][["Label"]] <- group_path$path_ID[folds_test[[i]]] - 1
  
}
# Paramètres de l'optimiseur
cont_opt <- list(mean = c(1, 1, 1), # Débute avec en moyenne, Alpha=1, Lambda=1, Lambda_bias=1
                 sd = c(1, 1, 1), # Débute avec en écart-type, Alpha=1, Lambda=1, Lambda_bias=1
                 conMat = rbind(diag(3), -diag(3)), # Optimisation linéaire conditionnée par la matrice du simplexe
                 conVec = c(5, 5, 5, 0, 0, 0), # 0<=alpha<=5, 0<=Lambda<=5, 0<=Lambda_bias<=5
                 sdThr = 0.1) # On suppose les hyperparamètres convergés lorsque tous les écart-types sont en-dessous de 0.1
p0 <- list() # Pré-initaisliation de la liste pour les variables discrètes
for (i in 1:36) {p0 <- c(p0, list(c(0.72, 0.28)))} # On souhaite 50% des features à la fin en moyenne, à moins que certaines variables ont une importance telle qu'elles ne peuvent être omises et seront forcément sélectionnées
disc_opt <- list(probs = p0,
                 smoothProb = 1.00, # On va tenter de converger rapidement ici pour la réalistion d'un proof of concept, mais sinon avec plus de temps on pourra réaliser un shrinkage de 5% de la probabilité élite à chaque itération (smoothProb = 0.95)
                 probThr = 0.0001) # On suppose la sélection de features convergé lorsque toutes les probabilités sont en-dessous de 0.0001
n_family <- 250 # Le nombre d'estimations par itération de l'optimiseur
elite <- 0.1 # Le nombre d'élites par itération qui dictent la loi dans l'entropie croisée
iterations <- 21 # Le nombre d'itérations d'optimisation (plus un pour l'itération d'initialisation)
early_stop <- 5 # Arrêt prématuré lorsque la fonction de perte (ici l'inexactitude de la classification) ne diminue pas après X itérations
# Pré-initialisation de la variable de logging
iters <- 0 # Suivi de l'itération
best_error <- 1 # Suivi de la perte (inexactitude), initialisé à une très mauvaise valeur possible
best_lloss <- 9.9999 # Suivi de la perte (logarithmique), initialisé à une très mauvaise valeur possible
best_score <- 9.9999 # Suivi de la perte (inexactitude * logarithmique), initialisé à une très mauvaise valeur possible
error_list <- data.frame(Iteration = 1:(n_family * iterations),
                         Error_1 = numeric(n_family * iterations),
                         Error_2 = numeric(n_family * iterations),
                         Error_3 = numeric(n_family * iterations),
                         Error_Mean = numeric(n_family * iterations),
                         Loss_1 = numeric(n_family * iterations),
                         Loss_2 = numeric(n_family * iterations),
                         Loss_3 = numeric(n_family * iterations),
                         Loss_Mean = numeric(n_family * iterations),
                         Score = numeric(n_family * iterations),
                         Features_n = numeric(n_family * iterations),
                         Alpha = numeric(n_family * iterations),
                         Lambda = numeric(n_family * iterations),
                         Lambda_bias = numeric(n_family * iterations),
                         matrix(rep(0, n_family * iterations * 36), ncol = 36),
                         Logging = rep(0, n_family * iterations))
colnames(error_list)[15:50] <- c(paste0(rep(c(paste0("Coef", 1:4), paste0("Rési", 1:4)), 4), paste0("_", inverse.rle(list(lengths = rep(8, 4), values = 1:4)))), paste0("PosInitiale_", 1:4))
set.seed(0) # Fixation du seed aléatoire pour des résultats qui puissent être reproduits
# Optimisation par entropie croisée
best_weights <- CEoptim(CE_Features,
                        f.arg = list(train = training_data, # Données d'entrainement
                                     test = testing_data), # Données de validation
                        maximize = FALSE, # Minimisation du problème
                        continuous = cont_opt,
                        discrete = disc_opt,
                        N = n_family,
                        rho = elite,
                        verbose = TRUE,
                        iterThr = iterations - 1,
                        noImproveThr = early_stop)
Number of continuous variables: 3  
Number of discrete variables: 36 
conMat= 
     [,1] [,2] [,3]
[1,]    1    0    0
[2,]    0    1    0
[3,]    0    0    1
[4,]   -1    0    0
[5,]    0   -1    0
[6,]    0    0   -1
conVec= 
[1] 5 5 5 0 0 0
smoothMean: 1 smoothSd: 1 smoothProb: 1 
N: 250 rho: 0.1 iterThr: 20 sdThr: 0.1 probThr 1e-04 
iter: 0  opt: 0.3749418 maxSd: 0.9782853 maxProbs: 0.48
iter: 1  opt: 0.3386276 maxSd: 0.8361778 maxProbs: 0.48
iter: 2  opt: 0.3114675 maxSd: 0.56788 maxProbs: 0.48
iter: 3  opt: 0.3067754 maxSd: 0.573345 maxProbs: 0.44
iter: 4  opt: 0.291244 maxSd: 0.6706154 maxProbs: 0.48
iter: 5  opt: 0.290563 maxSd: 0.5828973 maxProbs: 0.48
iter: 6  opt: 0.2811712 maxSd: 0.6613192 maxProbs: 0.44
iter: 7  opt: 0.2793675 maxSd: 0.5748965 maxProbs: 0.48
iter: 8  opt: 0.2741055 maxSd: 0.5865809 maxProbs: 0.48
iter: 9  opt: 0.2714183 maxSd: 0.4431069 maxProbs: 0.36
iter: 10  opt: 0.2714183 maxSd: 0.3804242 maxProbs: 0.24
iter: 11  opt: 0.2700002 maxSd: 0.3880721 maxProbs: 0.16
iter: 12  opt: 0.2700002 maxSd: 0.3490389 maxProbs: 0.08
iter: 13  opt: 0.2700002 maxSd: 0.4694499 maxProbs: 0
iter: 14  opt: 0.2700002 maxSd: 0.5475662 maxProbs: 0
iter: 15  opt: 0.2700002 maxSd: 0.6524463 maxProbs: 0
iter: 16  opt: 0.2699669 maxSd: 0.5058152 maxProbs: 0
iter: 17  opt: 0.26996 maxSd: 0.4172189 maxProbs: 0
iter: 18  opt: 0.269868 maxSd: 0.4959221 maxProbs: 0
iter: 19  opt: 0.2698675 maxSd: 0.5982446 maxProbs: 0
# Enregistrement du log détaillé
fwrite(error_list, "optim/error_raw.csv")
# Enregistrement du log détaillé et nettoyé des éléments inutiles
error_list <- error_list[error_list$Logging == 1, ]
fwrite(error_list, "optim/error_clean.csv")
# Enregistrement de la varible contenant l'optimisation
saveRDS(best_weights, "optim/optimized.rds")
# Affichage des résultats
x <- best_weights$optimizer$continuous # Récupération des hyperparamètres
y <- best_weights$optimizer$discrete # Récupération des features sélectionnées
cat("  \nL'optimiseur a trouvé :  \n  - Meilleure Inexactitude = ", best_error, "  \n  - Meilleure Perte Logarithmique = ", best_lloss, "\n  - alpha = ", x[1], "  \n  - lambda = ", x[2], "  \n - lambda_bias = ", x[3], "  \n  - features = ", sum(as.boolean(y)), " (binary = ", paste(y, collapse = ""), ")  \n  \nFeatures utilisées :  \n", sep = "")
  
L'optimiseur a trouvé :  
  - Meilleure Inexactitude = 0.2838657  
  - Meilleure Perte Logarithmique = 0.9425253
  - alpha = 0.3762587  
  - lambda = 0.221014  
 - lambda_bias = 0.6638027  
  - features = 13 (binary = 000010000001001011111010001100000110)  
  
Features utilisées :  
dput(c(paste0(rep(c(paste0("Coef", 1:4), paste0("Rési", 1:4)), 4), paste0("_", inverse.rle(list(lengths = rep(8, 4), values = 1:4)))), paste0("PosInitiale_", 1:4))[as.boolean(y)]) # Affichage des noms des features
c("Rési1_1", "Coef4_2", "Rési3_2", "Coef1_3", "Coef2_3", "Coef3_3", 
"Coef4_3", "Rési1_3", "Rési3_3", "Coef3_4", "Coef4_4", "PosInitiale_2", 
"PosInitiale_3")
# Temps nécessaire
timing(CurrentTime, "Optimisation par entropie croisée")
Temps d'exécution de la tâche 'Optimisation par entropie croisée' : 1848.70 secondes.  

6.2 Visualisation de l’évolution de l’optimisation

On peut visualiser l’évolution de l’optimisation à partir de graphiques. On remarque une convergence plutôt rapide des pertes (inexactitude, logloss).

On remarquera l’inversion entre du niveau des courbes des salles 1 et 2 lorsqu’on oppose perte logarithmique et inexactitude. En effet, il n’existe aucune corrélation directe entre la perte logarithmique et l’inexactitude.

ggplotly(ggplot(data = error_list[, c("Iteration", "Error_Mean")], aes_string(x = "Iteration", y = "Error_Mean")) + geom_line() + theme_bw() + labs(title = "Evolution de l'inexactitude par rapport au nombre d'itérations de modélisations en moyenne"), width = 960, height = 720)

ggplotly(ggplot(data = data.frame(Iteration = rep(1:nrow(error_list), 3), Error = c(error_list[["Error_1"]], error_list[["Error_2"]], error_list[["Error_3"]]), Salle = as.factor(inverse.rle(list(lengths = rep(nrow(error_list), 3), values = 1:3)))), aes_string(x = "Iteration", y = "Error", color = "Salle")) + geom_line() + theme_bw() + labs(title = "Evolution de l'inexactitude par rapport au nombre d'itérations de modélisation par salle"), width = 960, height = 720)

ggplotly(ggplot(data = error_list[, c("Iteration", "Loss_Mean")], aes_string(x = "Iteration", y = "Loss_Mean")) + geom_line() + theme_bw() + labs(title = "Evolution du logloss par rapport au nombre d'itérations de modélisations en moyenne"), width = 960, height = 720)

ggplotly(ggplot(data = data.frame(Iteration = rep(1:nrow(error_list), 3), Loss = c(error_list[["Loss_1"]], error_list[["Loss_2"]], error_list[["Loss_3"]]), Salle = as.factor(inverse.rle(list(lengths = rep(nrow(error_list), 3), values = 1:3)))), aes_string(x = "Iteration", y = "Loss", color = "Salle")) + geom_line() + theme_bw() + labs(title = "Evolution du logloss par rapport au nombre d'itérations de modélisation par salle"), width = 960, height = 720)

6.3 Création des features

Nous pouvons créer les features à partir des features sélectionnées.

# Compteur de temps
CurrentTime <- timer() # Préparation de l'évaluation des modèles avec features sélectionnées
# Où sauvegarder les fichiers ?
file_tag <- "4_data/"
# Initialisation de la variable qui accueillera la précision
accuracy <- data.frame(matrix(nrow = 16, ncol = 13))
colnames(accuracy) <- c("Fold", "xgb_LinearModel", "xgb_DecisionTree", "xgb_RandomForest", "xgb_GradientBoosting", "h2o_LinearModel", "h2o_DecisionTree", "h2o_RandomForest", "h2o_GradientBoosting", "h2o_NN_32x6_ReLU", "h2o_NN_32x6_Soft", "h2o_NN_16x16x6_ReLU", "h2o_NN_16x16x6_Soft")
accuracy[, 1] <- c("Fold_1v2", "Fold_1v3", "Fold_2v1", "Fold_2v3", "Fold_3v1", "Fold_3v2", "Fold_1v23", "Fold_2v13", "Fold_3v12", "Fold_12v3", "Fold_13v2", "Fold_23v1", "Moyenne_1c1", "Moyenne_1c2", "Moyenne_2c1", "Moyenne")
# Initialisation des folds pour la cross-validation
folds_train <- list()
folds_test <- list()
training_data <- list()
testing_data <- list()
training_xgb <- list()
testing_xgb <- list()
training_h2o <- list()
testing_h2o <- list()
combinations_train <- c(list(1, 1, 2, 2, 3, 3), combn(3, 1, simplify = FALSE), combn(3, 2, simplify = FALSE))
combinations_test <- c(list(2, 3, 1, 3, 1, 2), rev(combn(3, 2, simplify = FALSE)), rev(combn(3, 1, simplify = FALSE)))
temp_factors <- as.factor(group_path$path_ID)
# Création des données d'entrainement et de validation
for (i in 1:12) {
  
  # Création des folds d'entrainement et de validation
  folds_train[[i]] <- which(group_room[["dataset_ID"]] %in% combinations_train[[i]])
  folds_test[[i]] <- which(group_room[["dataset_ID"]] %in% combinations_test[[i]])
  
  # Recherche et suppression du label 3 lorsque la salle 1 est isolée (soit en train on enlève en test, soit en test on enlève en train)
  if ((length(combinations_train[[i]]) == 1) & (combinations_train[[i]][1] == 1)) {
    folds_test[[i]] <- folds_test[[i]][group_path$path_ID[folds_test[[i]]] != 3]
  }
  if ((length(combinations_test[[i]]) == 1) & (combinations_test[[i]][1] == 1)) {
    folds_train[[i]] <- folds_train[[i]][group_path$path_ID[folds_train[[i]]] != 3]
  }
  
  # Création des données d'entrainement et de validation
  training_data[[i]] <- mini_lm[folds_train[[i]], which(best_weights$optimizer$discrete == 1)]
  testing_data[[i]] <- mini_lm[folds_test[[i]], which(best_weights$optimizer$discrete == 1)]
  
  # Enregistrement des données CSV
  fwrite(training_data[[i]], paste0(file_tag, "trainNL_", sprintf("%02d", i), ".csv"))
  fwrite(testing_data[[i]], paste0(file_tag, "testNL_", sprintf("%02d", i), ".csv"))
  
  # Transformation des données au format approprié pour xgboost
  training_xgb[[i]] <- xgb.DMatrix(data = as.matrix(training_data[[i]]), label = group_path$path_ID[folds_train[[i]]] - 1)
  testing_xgb[[i]] <- xgb.DMatrix(data = as.matrix(testing_data[[i]]), label = group_path$path_ID[folds_test[[i]]] - 1)
  
  # Dumping des datasets binaires xgboost
  xgb.DMatrix.save(training_xgb[[i]], paste0(file_tag, "trainL_", sprintf("%02d", i), ".data"))
  xgb.DMatrix.save(testing_xgb[[i]], paste0(file_tag, "testL_", sprintf("%02d", i), ".data"))
  
  # Transformation des données au format approprié pour H2O
  training_h2o[[i]] <- as.h2o(cbind(Label = temp_factors[folds_train[[i]]], training_data[[i]]))
  testing_h2o[[i]] <- as.h2o(cbind(Label = temp_factors[folds_test[[i]]], testing_data[[i]]))
  
  # Enregistrement des frames H2O (CSV + Label)
  h2o.exportFile(training_h2o[[i]], paste0(file_tag, "trainL_", sprintf("%02d", i), ".csv"), force = TRUE)
  h2o.exportFile(testing_h2o[[i]], paste0(file_tag, "testL_", sprintf("%02d", i), ".csv"), force = TRUE)
  
}
# Temps nécessaire
timing(CurrentTime, "Préparation de l'évaluation des modèles avec features sélectionnées")
Temps d'exécution de la tâche 'Préparation de l'évaluation des modèles avec features sélectionnées' : 15.58 secondes.  

6.4 Entrainement des douze modèles

Nous pouvons maintenant entrainer les modèles sur notre sélection de features.

# Compteur de temps
CurrentTime <- timer() # Chunk Création et évaluation des douze modèles avec features sélectionnées
# Où sauvegarder les fichiers ?
file_tag <- "4_models/"
file_h2o <- "4_models"
# Boucle d'évaluation
for (i in 1:12) {
  
  # Entrainement du modèle de régression logistique (xgboost)
  temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
                                  test = testing_xgb[[i]],
                                  booster = "gblinear", # Linéaire
                                  nrounds = 1000000, # Arrêté au meilleur résultat
                                  num_parallel_trees = 1)
  xgb.dump(model = temp_model, # Modèle à enregistrer
           fname = paste0(file_tag, "xgb_glm_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
           with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
           dump_format = "json") # Dump au format json, ré-utilisable
  accuracy[i, 2] <- 1 - temp_model$evaluation_log[[2]][temp_model$best_iteration] # Récupération du meilleur résultat
  
  # Entrainement du modèle d'arbre de décision (xgboost)
  temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
                                  test = testing_xgb[[i]],
                                  booster = "gbtree", # Non-linéaire
                                  nrounds = 1, # Un seul arbre
                                  num_parallel_trees = 1)
  xgb.dump(model = temp_model, # Modèle à enregistrer
           fname = paste0(file_tag, "xgb_dt_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
           with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
           dump_format = "json") # Dump au format json, ré-utilisable
  accuracy[i, 3] <- 1 - temp_model$evaluation_log[[2]][1] # Récupération du meilleur résultat
  
  # Entrainement du modèle de Random Forest (xgboost)
  temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
                                  test = testing_xgb[[i]],
                                  booster = "gbtree", # Non-linéaire
                                  nrounds = 1, # Une seule itération
                                  num_parallel_trees = 200) # De 200 arbres
  xgb.dump(model = temp_model, # Modèle à enregistrer
           fname = paste0(file_tag, "xgb_rf_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
           with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
           dump_format = "json") # Dump au format json, ré-utilisable
  accuracy[i, 4] <- 1 - temp_model$evaluation_log[[2]] # Récupération du meilleur résultat
  
  # Entrainement du modèle d'arbre de décision boosté avec protection contre l'overfitting (xgboost)
  temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
                                  test = testing_xgb[[i]],
                                  booster = "gbtree", # Non-linéaire
                                  nrounds = 1000000, # Arrêté au meilleur résultat
                                  num_parallel_trees = 1)
  xgb.dump(model = temp_model, # Modèle à enregistrer
           fname = paste0(file_tag, "xgb_gbt_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
           with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
           dump_format = "json") # Dump au format json, ré-utilisable
  accuracy[i, 5] <- 1 - temp_model$evaluation_log[[2]][temp_model$best_iteration] # Récupération du meilleur résultat
  
  # Entrainement du modèle de régression logistique (h2o)
  temp_model <- h2o.glm(y = 1,
                        training_frame = training_h2o[[i]],
                        validation_frame = testing_h2o[[i]],
                        model_id = paste0("h2o_glm_", sprintf("%02d", i)), # Nom du modèle
                        max_iterations = 100, # 100 itérations d'optimisation
                        solver = "IRLSM", # Solveur par défaut
                        standardize = FALSE, # Pas de standardisation puisque [-1, 1]
                        family = "multinomial", # Classification multi-classe
                        seed = 0, # Reproduction des résultats
                        intercept = TRUE)
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 6] <- temp_model@model$validation_metrics@metrics$hit_ratio_table[1, 2]
  
  # Entrainement du modèle d'arbre de décision (h2o)
  temp_model <- h2o.randomForest(y = 1,
                                 training_frame = training_h2o[[i]],
                                 validation_frame = testing_h2o[[i]],
                                 model_id = paste0("h2o_dt_", sprintf("%02d", i)), # Nom du modèle
                                 sample_rate = 1, # Toutes les observations seront prises en compte pour le seul arbre de décision
                                 mtries = sum(best_weights$optimizer$discrete), # Toutes les features seront prises en compte pour le seul arbre de décision
                                 ntrees = 1, # Un seul arbre
                                 seed = 0) # Reproduction des résultats
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 7] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
  # Entrainement du modèle de Random Forest (h2o)
  temp_model <- h2o.randomForest(y = 1,
                                 training_frame = training_h2o[[i]],
                                 validation_frame = testing_h2o[[i]],
                                 model_id = paste0("h2o_rf_", sprintf("%02d", i)), # Nom du modèle
                                 sample_rate = 0.632, # Bootstrapping .632 pour chaque arbre de décision
                                 mtries = -1, # sqrt(36) features seront prises en compte pour chaque arbre de décision
                                 ntrees = 200, # 200 arbres
                                 seed = 0) # Reproduction des résultats
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 8] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
  # Entrainement du modèle d'arbre de décision boosté avec protection contre l'overfitting (h2o)
  temp_model <- h2o.gbm(y = 1,
                      training_frame = training_h2o[[i]],
                      validation_frame = testing_h2o[[i]],
                      model_id = paste0("h2o_gbt_", sprintf("%02d", i)), # Nom du modèle
                      distribution = "multinomial", # Classification multi-classe
                      sample_rate = 1, # Pas de processus stochastique
                      ntrees = 100, # 100 itérations de boosting au maximum
                      score_each_iteration = TRUE, # Noter la valeur de chaque itération
                      stopping_rounds = 10, # Arrêt après 10 itérations sans amélioraton de la métrique
                      stopping_metric = "misclassification", # Surveiller l'inexactitude de la classification pour l'arrêt
                      stopping_tolerance = 0.00001, # Arrêter lorsque la métrique stagne de 0.001%
                      seed = 0) # Reproduction des résultats
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 9] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
  # Entrainement du réseau de neurones à architecture 32x6 + ReLU (h2o)
  temp_model <- h2o_nn_train(train = training_h2o[[i]],
                             test = testing_h2o[[i]],
                             model_id = paste0("h2o_nn_32x6_ReLU_", sprintf("%02d", i)), # Nom du modèle
                             activation = "Rectifier", # ReLU
                             hidden = 32) # Architecture 32x6
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 10] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
  # Entrainement du réseau de neurones à architecture 32x6 + Tanh (h2o)
  temp_model <- h2o_nn_train(train = training_h2o[[i]],
                             test = testing_h2o[[i]],
                             model_id = paste0("h2o_nn_32x6_Tanh_", sprintf("%02d", i)), # Nom du modèle
                             activation = "Tanh", # "Sigmoide"
                             hidden = 32) # Architecture 32x6
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 11] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
  # Entrainement du réseau de neurones à architecture 16x16x6 + ReLU (h2o)
  temp_model <- h2o_nn_train(train = training_h2o[[i]],
                             test = testing_h2o[[i]],
                             model_id = paste0("h2o_nn_16x16x6_ReLU_", sprintf("%02d", i)), # Nom du modèle
                             activation = "Rectifier", # ReLU
                             hidden = c(16, 16)) # Architecture 16x16x6
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 12] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
  # Entrainement du réseau de neurones à architecture 16x16x6 + Tanh (h2o)
  temp_model <- h2o_nn_train(train = training_h2o[[i]],
                             test = testing_h2o[[i]],
                             model_id = paste0("h2o_nn_16x16x6_Tanh_", sprintf("%02d", i)), # Nom du modèle
                             activation = "Tanh", # "Sigmoide"
                             hidden = c(16, 16)) # Architecture 16x16x6
  h2o.download_pojo(temp_model, # Modèle à enregistrer
                    path = file_h2o, # Où enregistrer le modèle ?
                    get_jar = FALSE) # Pas de fichier .jar
  accuracy[i, 13] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
  
}
# Temps nécessaire
timing(CurrentTime, "Création et évaluation des douze modèles avec features sélectionnées")
Temps d'exécution de la tâche 'Création et évaluation des douze modèles avec features sélectionnées' : 167.14 secondes.  

6.5 Affichage des résultats

Les modèles sont bien plus performants qu’initialement après une sélection des features. On atteint désormais une moyenne haute d’exactitude de 68%, contre 63% auparavant. C’est un avancement important dans la performance des modèles, et on sait qu’on peut encore ajouter 3-4% de performance aux modèles les plus performants avec un tuning des hyperparamètres.

On remarque les mêmes modèles sont toujours les plus performants : ce sont les modèles linéaires !

# Moyenne des résultats
for (i in 2:13) {
  accuracy[13, i] <- mean(accuracy[1:6, i])
  accuracy[14, i] <- mean(accuracy[7:9, i])
  accuracy[15, i] <- mean(accuracy[10:12, i])
  accuracy[16, i] <- mean(accuracy[13:15, i])
}
# Enregistrement des scores
fwrite(accuracy, "scores/4_models.csv")
# Affichage des résultats dans un tableau interactif
to_print <- data.table(t(accuracy[13:16, -1])) # Préparation des données à mettre sur table
colnames(to_print) <- c("1 contre 1", "1 contre 2", "2 contre 1", "Moyenne") # Remise des noms des colonnes
row.names(to_print) <- colnames(accuracy)[-1] # Remise des noms des lignes
datatable(to_print,
          filter = "top", # Filtrage au-dessus de la table
          class = "cell-border stripe", # CSS
          extensions = c("ColReorder",
                         "RowReorder"), # Reordonner manuellement à la main
          options = list(pageLength = 12, # Page affichant 12 lignes
                         order = list(list(4, "desc")), # Ordonner par défaut par l'exactitude moyenne
                         colReorder = TRUE, # Plugin
                         rowReorder = TRUE)) %>% # Plugin
  formatStyle(c("1 contre 1", "1 contre 2", "2 contre 1"),
                  background = styleColorBar(c(0, 1), 'lightgreen'), # Couleur vert clair pour les métriques par fold
                  backgroundSize = '100% 90%',
                  backgroundRepeat = 'no-repeat',
                  backgroundPosition = 'center') %>%
  formatStyle("Moyenne",
              background = styleColorBar(c(0, 1), 'pink'), # Couleur rose pour la métrique de moyenne
              backgroundSize = '100% 90%',
              backgroundRepeat = 'no-repeat',
              backgroundPosition = 'center') %>%
  formatPercentage(columns = c("1 contre 1", "1 contre 2", "2 contre 1"),
              digits = 8) %>%
  formatPercentage(columns = "Moyenne",
              digits = 8)

# Affichage des résultats dans un tableau statique
formattable(accuracy[, c(1, 2:5)], list(formattable::area(col = xgb_LinearModel:xgb_GradientBoosting) ~ color_bar("orange")))

formattable(accuracy[, c(1, 6:9)], list(formattable::area(col = h2o_LinearModel:h2o_GradientBoosting) ~ color_bar("cyan")))

formattable(accuracy[, c(1, 10:13)], list(formattable::area(col = h2o_NN_32x6_ReLU:h2o_NN_16x16x6_Soft) ~ color_bar("yellow")))

6.6 Analyse du modèle de régression logistique

Lorsqu’on utilise les meilleurs paramètres pour le modèle de régression logistique, on obtient les résultats ci-dessous.

La salle 1 semble toujours causer des problèmes, il est possible qu’il y a toujours un mauvais conditionnement des données issues de cette salle. La salle 2 et 3 semblent les plus stables, et semble informer que :

  • L’utilisation des 4 ancres permet d’obtenir le plus d’information, ce qui a été empiriquement vérifié par Bacciu et al. dans An experimental characterization of reservoir computing in ambient assisted living applications, et vérifié également ici (présence de toutes les ancres dans les features sélectionnées)
  • Les features peuvent être en contradiction selon les salles (par exemple, Resi1_1 semble inutile pour le label 3 de la salle 2 avec un coefficient quasiment à 0, mais intégralement utile pour le même label pour la salle 3)
  • L’entrainement de la salle 1 est si rapide que l’overfitting apparait immédiatement si le nombre d’itérations augmente à un nombre supérieur à 1 chiffre.

Il est clair également que la chemin 3 a été entièrement perdue en faveur de toutes les autres salles. Cela est une bonne et une mauvaise chose, car le modèle semble privilégier la prédiction de la chemin 1 en tout point (35% des prédictions pour uniquement 25% des valeurs). Il est tout à fait plausible que le chemin 3 soit difficile à prédire et que l’optimisation a privilégié les autres chemins, puisqu’ils pourraient être plus facile à optimiser linéairement.

# Compteur de temps
CurrentTime <- timer() # Chunk Préparation de l'analyse du dernier modèle de régression logistique
# Pré-initialisation des variables
predictedValues <- matrix(nrow = 314, ncol = 6)
evolution <- list()
temp_dt <- list()
temp_means <- data.frame(Feature = c(paste0(rep(c(paste0("Coef", 1:4), paste0("Rési", 1:4)), 4), paste0("_", inverse.rle(list(lengths = rep(8, 4), values = 1:4)))), paste0("PosInitiale_", 1:4))[which(best_weights$optimizer$discrete == 1)],
                         Fold_1 = numeric(sum(best_weights$optimizer$discrete == 1)),
                         Fold_2 = numeric(sum(best_weights$optimizer$discrete == 1)),
                         Fold_3 = numeric(sum(best_weights$optimizer$discrete == 1)),
                         Fold_Mean = numeric(sum(best_weights$optimizer$discrete == 1)),
                         Feature_Mean = numeric(sum(best_weights$optimizer$discrete == 1)),
                         Feature_SD = numeric(sum(best_weights$optimizer$discrete == 1)))
# Boucle d'entrainement 2 contre 1
for (i in 10:12) {
  
  # Entrainement d'un modèle linéaire
  temp_model <- xgb.train(data = training_xgb[[i]],
                          watchlist = list(test = testing_xgb[[i]]), # Données de validation
                          num_class = 6, # 6 classe de classification
                          nthread = 1, # 1 thread pour la reproduction des résultats
                          nrounds = 1000, # 1000 itérations, où arrêt prématuré
                          alpha = best_weights$optimizer$continuous[1], # Régularisation L1 (Lasso)
                          lambda = best_weights$optimizer$continuous[2], # Régularisation L2 (Ridge)
                          lambda_bias = best_weights$optimizer$continuous[3], # Régularisation L2 du biais (Ridge)
                          eta = 0.10, # Shrinkage pour le boosting
                          booster = "gblinear", # Type d'entrainement : linéaire ou non-linéaire
                          objective = "multi:softprob", # Gradient/Hessian pour l'optimisation par Gradient Descent
                          eval_metric = "merror", # Inexactitude de la classification (cette métrique est optimisée par xgboost)
                          maximize = FALSE, # Minimisation de l'erreur
                          early_stopping_rounds = 50, # Arrêt après 50 itérations sans amélioration de la métrique
                          verbose = FALSE, # Sans print des itérations
                          callbacks = list(cb.evaluation.log())) # Logging des données d'entrainement pour pouvoir récupérer les métriques)
  
  # Enregistrement du log
  evolution[[i - 9]] <- cbind(temp_model$evaluation_log, Fold = rep(13 - i, temp_model$niter))
  
  # Entrainement du meilleur modèle (obtention des meilleurs coefficients)
  temp_model <- xgb.train(data = training_xgb[[i]],
                          watchlist = list(test = testing_xgb[[i]]), # Données de validation
                          num_class = 6, # 6 classe de classification
                          nthread = 1, # 1 thread pour la reproduction des résultats
                          nrounds = temp_model$best_iteration, # Meilleure itération
                          alpha = best_weights$optimizer$continuous[1], # Régularisation L1 (Lasso)
                          lambda = best_weights$optimizer$continuous[2], # Régularisation L2 (Ridge)
                          lambda_bias = best_weights$optimizer$continuous[3], # Régularisation L2 du biais (Ridge)
                          eta = 0.10, # Shrinkage pour le boosting
                          booster = "gblinear", # Type d'entrainement : linéaire ou non-linéaire
                          objective = "multi:softprob", # Gradient/Hessian pour l'optimisation par Gradient Descent
                          eval_metric = "merror", # Inexactitude de la classification (cette métrique est optimisée par xgboost)
                          maximize = FALSE, # Minimisation de l'erreur
                          early_stopping_rounds = 99999, # Sans arrêt
                          verbose = FALSE, # Sans print des itérations
                          callbacks = list(cb.evaluation.log())) # Logging des données d'entrainement pour pouvoir récupérer les métriques)
  
  # Prédiction du modèle linéaire
  predictedValues[folds_test[[i]], ] <- t(matrix(predict(temp_model, testing_xgb[[i]], ntreelimit = 0), nrow = 6))
  
  # Calcul et formattage de l'importance des variables
  temp_importance <- data.table(Feature = temp_means[["Feature"]],
                                matrix(xgb.importance(model = temp_model)$Weight, ncol = 6))
  colnames(temp_importance) <- c("Feature", paste0("Label_", 1:6))
  temp_importance[["Sign"]] <- paste0(ifelse(temp_importance[[2]] >= 0, "+", "-"), ifelse(temp_importance[[3]] >= 0, "+", "-"), ifelse(temp_importance[[4]] >= 0, "+", "-"), ifelse(temp_importance[[5]] >= 0, "+", "-"), ifelse(temp_importance[[6]] >= 0, "+", "-"), ifelse(temp_importance[[7]] >= 0, "+", "-"))
  temp_importance[, 2:7] <- abs(temp_importance[, 2:7, with = FALSE])
  temp_means[[14 - i]] <- rowMeans(temp_importance[, 2:7, with = FALSE])
  temp_importance[[paste0("Fold_", 13 - i, "_Mean")]] <- temp_means[[14 - i]]
  
  # Enregistrement sous forme de tableau interactif
  temp_dt[[i - 9]] <- datatable(temp_importance,
        filter = "top", # Filtrage au-dessus de la table
        class = "cell-border stripe", # CSS
        extensions = c("ColReorder",
                       "RowReorder"), # Reordonner manuellement à la main
        options = list(pageLength = 13, # Page affichant 13 lignes
                       order = list(list(9, "desc")), # Ordonner par défaut par les facteurs ayant le poids le plus gros
                       colReorder = TRUE, # Plugin
                       rowReorder = TRUE)) %>% # Plugin
  formatStyle(paste0("Label_", 1:6),
                  background = styleColorBar(range(temp_importance[, 2:7, with = FALSE]), 'lightblue'), # Couleur bleue pour le coefficient
                  backgroundSize = '100% 90%',
                  backgroundRepeat = 'no-repeat',
                  backgroundPosition = 'center') %>%
  formatStyle(paste0("Fold_", 13 - i, "_Mean"),
              background = styleColorBar(range(temp_importance[[9]]), 'pink'), # Couleur rose pour la métrique de moyenne
              backgroundSize = '100% 90%',
              backgroundRepeat = 'no-repeat',
              backgroundPosition = 'center') %>%
  formatRound(columns = c(paste0("Label_", 1:6), paste0("Fold_", 13 - i, "_Mean")),
              digits = 6)
  
}
# Calcul du poids moyen affecté à chaque feature
temp_means[[5]] <- rowMeans(temp_means[, 2:4]) # Poids moyen
temp_means[[6]] <- apply(mini_lm[, which(best_weights$optimizer$discrete == 1)], 2, function(x) {mean(x)}) # Moyenne de la feature dans les données
temp_means[[7]] <- apply(mini_lm[, which(best_weights$optimizer$discrete == 1)], 2, function(x) {sd(x)}) # Ecart-type de la feature dans les données
# Prépration du tableau interactif sur les poids moyens agrégés
temp_dt[[4]] <- datatable(temp_means,
        filter = "top", # Filtrage au-dessus de la table
        class = "cell-border stripe", # CSS
        extensions = c("ColReorder",
                       "RowReorder"), # Reordonner manuellement à la main
        options = list(pageLength = 13, # Page affichant 13 lignes
                       order = list(list(5, "desc")), # Ordonner par défaut par les facteurs ayant le poids le plus gros en moyenne
                       colReorder = TRUE, # Plugin
                       rowReorder = TRUE)) %>% # Plugin
  formatStyle(paste0("Fold_", 1:3),
                  background = styleColorBar(range(temp_means[, 2:4]), 'lightblue'), # Couleur bleue pour le coefficient
                  backgroundSize = '100% 90%',
                  backgroundRepeat = 'no-repeat',
                  backgroundPosition = 'center') %>%
  formatStyle("Fold_Mean",
              background = styleColorBar(range(temp_means[[5]]), 'pink'), # Couleur rose pour la métrique de moyenne
              backgroundSize = '100% 90%',
              backgroundRepeat = 'no-repeat',
              backgroundPosition = 'center') %>%
  formatStyle("Feature_Mean",
              background = styleColorBar(range(temp_means[[6]]), 'lightgreen'), # Couleur verte pour la moyenne des features
              backgroundSize = '100% 90%',
              backgroundRepeat = 'no-repeat',
              backgroundPosition = 'center') %>%
  formatStyle("Feature_SD",
              background = styleColorBar(range(temp_means[[7]]), 'orange'), # Couleur verte pour l'écart-type des features
              backgroundSize = '100% 90%',
              backgroundRepeat = 'no-repeat',
              backgroundPosition = 'center') %>%
  formatRound(columns = c(paste0("Fold_", 1:3), "Fold_Mean", "Feature_Mean", "Feature_SD"),
              digits = 6)
# Dépivotage du log
evolution <- rbindlist(evolution)
colnames(evolution) <- c("Iteration", "Exactitude", "Fold")
evolution$Exactitude <- 1 - evolution$Exactitude
evolution$Fold <- as.factor(evolution$Fold)
# Prédiction à partir des probabilités
predictedLabel <- data.frame(Label = group_path$path_ID, Prediction = apply(predictedValues, 1, function(x) {which.max(x)}))
timing(CurrentTime, "Préparation de l'analyse du dernier modèle de régression logistique")
Temps d'exécution de la tâche 'Préparation de l'analyse du dernier modèle de régression logistique' : 7.61 secondes.  
# Affichage de l'évolution de la performance du modèle selon le nombre d'itération, sous forme de plot interactif
ggplotly(ggplot(data = evolution, aes_string(x = "Iteration", y = "Exactitude", group = "Fold", color = "Fold")) + geom_line() + geom_point() + scale_color_brewer(palette = "Set2") + theme_bw() + labs(title = "Evolution de l'exactitude par rapport au nombre d'itérations d'entrainement"), width = 960, height = 720)

# Affichage de la matrice de confusion sous forme de plot interactif
confusion_mat <- expand.grid(Label = 1:6, Prediction = 1:6)
confusion_mat <- merge(confusion_mat, data.table(predictedLabel)[, list(Freq = sum(.N)), by = list(Label, Prediction)], by = c("Label", "Prediction"), all.x = TRUE)
confusion_mat[["Freq"]][is.na(confusion_mat[["Freq"]])] <- 0
confusion_mat[["Label"]] <- as.factor(confusion_mat[["Label"]])
confusion_mat[["Prediction"]] <- as.factor(confusion_mat[["Prediction"]])
ggplotly(ggplot() + geom_rect(data = data.frame(cent = 1:6), size = 2, fill = NA, colour = "black", aes(xmin = cent - 0.5, xmax = cent + 0.5, ymin = cent - 0.5, ymax = cent + 0.5)) + geom_tile(data = confusion_mat, aes_string(x = "Label", y = "Prediction", fill = "Freq")) + geom_text(data = confusion_mat, aes_string(x = "Label", y = "Prediction", label = "Freq")) + scale_x_discrete(name = "Trajectoire Réelle") + scale_y_discrete(name = "Trajectoire Prédite") + scale_fill_gradientn(colours = rev(brewer.pal_extended(3, "PiYG"))) + labs(title = "Matrice de Confusion de la Trajectoire", fill = "Fréquence"), width = 960, height = 720)

# Affichage des tables à la fin car le formattage possède un bug inhérent lorsqu'on a plusieurs datatables (DT) dans le même chunk
# htmltools::tagList(temp_dt[[3]], temp_dt[[2]], temp_dt[[1]], temp_dt[[4]])

7 Conclusion

Nous avons fini le début de tache d’analyse après 6 heures de travail. Ce n’est qu’un début d’exploration, et les performances peuvent être bien meilleures par la création de features différentes. Nous aurions pu par exemple forcer diverses interactions entre les variables par des multiplications, ou encore créé des features d’une autre manière (exemples : prédiction des prochains points par série temporelle, utilisation uniquement d’un nombre restreint de points pour le calcul des features, utilisation des X derniers points comme features…).

Comme prévu durant la seconde analyse exploratoire avec la démonstration du problème de domaines de définition des variables (problème de linéarité), les modèles linéaires s’en sortent les meilleurs à la fin. Les modèles non-linéaires sont à la traine en performance, vu que :

  • Pour tout problème de modélisation, il est plus facile d’apprendre un problème linéaire qu’un problème non-linéaire
  • Pour tout problème de modélisation, si le problème se rapproche d’un problème linéaire, alors la performance d’un modèle linéaire sera supérieure à la performance d’un modèle non-linéaire dans la plupart des cas

De fait, l’important est de retenir que la performance du modèle dépend dans l’ordre de priorité :

  • La plus importante : les features
  • Peu important : les hyperparamètres

Voici le sommaire des résultats lorsqu’on a entrainé avec deux datasets contre une dataset :

# Chargement des scores enregistrés avec nettoyage
accuracy1 <- t(fread("scores/1_models.csv")[15, -1][1, c(1, 5, 2, 6, 3, 7, 4, 8, 9:12)])
accuracy2 <- t(fread("scores/2_models.csv")[15, -1][1, c(1, 5, 2, 6, 3, 7, 4, 8, 9:12)])
accuracy3 <- t(fread("scores/3_models.csv")[15, -1][1, c(1, 5, 2, 6, 3, 7, 4, 8, 9:12)])
accuracy4 <- t(fread("scores/4_models.csv")[15, -1][1, c(1, 5, 2, 6, 3, 7, 4, 8, 9:12)])
# Agrégation des scores
accuracy_agg <- data.table(Defaut = c(accuracy1[1:2], mean(accuracy1[9:10]), mean(accuracy1[11:12]), accuracy1[3:8]),
                           Approximation = c(accuracy2[1:2], mean(accuracy2[9:10]), mean(accuracy2[11:12]), accuracy2[3:8]),
                           Inversement = c(accuracy3[1:2], mean(accuracy3[9:10]), mean(accuracy3[11:12]), accuracy3[3:8]),
                           Selection = c(accuracy4[1:2], mean(accuracy4[9:10]), mean(accuracy4[11:12]), accuracy4[3:8]))
accuracy_agg[["Evolution"]] <- apply(accuracy_agg, 1, function(x) {max(x) - min(x)})
row.names(accuracy_agg) <- c("Régression logistique (xgb)",
                             "Régression logistique (h2o)",
                             "Réseau de neurones 32x6 (h2o)",
                             "Réseau de neurones 16x16x6 (h2o)",
                             "Arbre de décision (xgb)",
                             "Arbre de décision (h2o)",
                             "Forêt aléatoire (xgb)",
                             "Forêt aléatoire (h2o)",
                             "Arbres boostés (xgb)",
                             "Arbres boostés (h2o)")
# Affichage des scores dans un tableau interactif
datatable(accuracy_agg,
          filter = "top", # Filtrage au-dessus de la table
          class = "cell-border stripe", # CSS
          extensions = c("ColReorder",
                         "RowReorder"), # Reordonner manuellement à la main
          options = list(pageLength = 10, # Page affichant 10 lignes
                         colReorder = TRUE, # Plugin
                         rowReorder = TRUE)) %>% # Plugin
  formatStyle(c("Defaut", "Approximation", "Inversement", "Selection"),
                  background = styleColorBar(c(0, max(accuracy_agg[["Selection"]])), 'lightgreen'), # Couleur vert clair pour les métriques par fold
                  backgroundSize = '100% 90%',
                  backgroundRepeat = 'no-repeat',
                  backgroundPosition = 'center') %>%
  formatStyle(c("Evolution"),
                  background = styleColorBar(c(0, max(accuracy_agg[["Evolution"]])), 'pink'), # Couleur rose pour l'évolution de la métrique
                  backgroundSize = '100% 90%',
                  backgroundRepeat = 'no-repeat',
                  backgroundPosition = 'center') %>%
  formatPercentage(columns = c("Defaut", "Approximation", "Inversement", "Selection", "Evolution"),
              digits = 4)

En effet, on ne peut pas faire apprendre à un algorithme comment créer une feature qui n’existe pas en tant qu’entrée.

Il est tout à fait possible qu’un réseau de neurones avec une architecture plus avancée puisse faire beaucoup mieux, uniquement en utilisant les features initiales. C’est ce que Bacciu et al. ont réalisé dans An experimental characterization of reservoir computing in ambient assisted living applications spécifiquement pour prédire le changement de zone dans une salle (jusqu’à 99% d’exactitude).

LS0tDQp0aXRsZTogIkluZG9vciBVc2VyIE1vdmVtZW50IFByZWRpY3Rpb24gZnJvbSBSU1MgZGF0YSBEYXRhIFNldCINCm91dHB1dDoNCiAgcHJldHR5ZG9jOjpodG1sX3ByZXR0eToNCiAgICB0aGVtZTogYXJjaGl0ZWN0DQogICAgaGlnaGxpZ2h0OiBnaXRodWINCiAgICBjc3M6IHJlc2l6ZS5jc3MNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHNtb290aF9zY3JvbGw6IG5vDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDYNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBjb2xsYXBzZWQ6IG5vDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICBzbW9vdGhfc2Nyb2xsOiBubw0KICAgIHRoZW1lOiByZWFkYWJsZQ0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA2DQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjb2xsYXBzZWQ6IG5vDQogICAgY3NzOiByZXNpemUuY3NzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICBzbW9vdGhfc2Nyb2xsOiBubw0KICAgIHRoZW1lOiByZWFkYWJsZQ0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA2DQogICAgdG9jX2Zsb2F0OiB5ZXMNCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVhICViICVkICVZICVYJylgIg0KLS0tDQoNCiMgSW50cm9kdWN0aW9uDQoNCkNlY2kgZXN0IGwnYW5hbHlzZSBwb3VyIGxhIHTDomNoZSBkJ2FuYWx5c2UgZGUgY2xhc3NpZmljYXRpb24gbXVsdGktY2xhc3NlIHN1ciBsYSBkYXRhc2V0ICoiSW5kb29yIFVzZXIgTW92ZW1lbnQgUHJlZGljdGlvbiBmcm9tIFJTUyBkYXRhIERhdGEgU2V0IiouDQoNCkwnb2JqZWN0aWYgZXN0IGRlIHByw6lkaXJlIGxlIHBhdHRlcm4gZGVzIG1vdXZlbWVudHMgZGVzIHV0aWxpc2F0ZXVycyBkYW5zIHVuIGVudmlyb25tbWVudCBkZSB0cmF2YWlsIMOgIHRyYXZlcnMgZGVzIHPDqXJpZXMgdGVtcG9yZWxsZXMgZ8OpbsOpcsOpZXMgcGFyIHVuIHLDqXNlYXUgZGUgY2FwdGV1cnMgc2Fucy1maWxzLiBMZXMgZG9ubsOpZXMgY29udGllbm5lbnQgZGVzIHN0cmVhbXMgZGUgZG9ubsOpZXMgdGVtcG9yZWxsZXMsIGNvbnNpc3RhbnQgZW4gbGEgZm9yY2UgZGVzIHNpZ25hdXggcmFkaW9zIG1lc3Vyw6lzIGVudHJlIGxlcyBub2V1ZHMgZHUgcsOpc2VhdSBkZSBjYXB0ZXVycyBzYW5zLWZpbHMsIMOgIHVuZSBmcsOpcXVlbmNlIGRlIDggSHogcmVtYXBww6llcyBkYW5zIGwnaW50ZXJ2YWxsZSBbLTEsIDFdLg0KDQpDZSByw6lzZWF1IHNlIGNvbXBvc2UgZGUgNSBjYXB0ZXVyczoNCg0KKiA0IGFuY3LDqWVzIGRhbnMgbCdlbnZpcm9ubmVtZW50DQoqIDEgYW5jcsOpZSBzdXIgbCd1dGlsaXNhdGV1cg0KDQpMYSB0YWNoZSBuw6ljZXNzaXRlIGRlIHByw6lkaXJlIGxhIGNsYXNzZSBpbmRpcXVhbnQgbGEgdHJhamVjdG9pcmUgZCd1biB1dGlsaXNhdGV1ciwgcGFybWkgNiBtb3V2ZW1lbnRzIHNww6ljaWZpcXVlcy4NCg0KT24gZGlzcG9zZSBkZSAzMTQgc8OpcXVlbmNlcywgcG91ciB1biBub21icmUgdG90YWwgZGUgMTMxOTcgw6l0YXBlcy4gRGUgZmFpdCwgbGVzIHPDqXJpZXMgbidvbnQgcGFzIGZvcmPDqW1lbnQgbGEgbcOqbWUgZHVyw6llLg0KDQpBIG5vdGVyIDogbGEgcHJlbWnDqHJlIGRhdGFzZXQgbmUgcG9zc8OoZGUgcGFzIGxhIGNsYXNzZSAzIGR1ZSDDoCBkZXMgY29udHJhaW50ZXMgcGh5c2lxdWVzLiBFbiBjb25zw6lxdWVuY2UsIGlsIHNlcmEgaW50w6lyZXNzZW50IGRlIHbDqXJpZmllciB0b3V0ZSBkaWZmw6lyZW5jZSBwb3RlbnRpZWxsZSBlbnRyZSBsZXMgcHLDqWRpY3Rpb25zIHNlbG9uIGxlcyBkYXRhc2V0cyB1dGlsaXPDqWVzIHBvdXIgbCdlbnRyYWluZW1lbnQvdmFsaWRhdGlvbi4NCg0KTm90ZTogbGUgQ1NTIGEgw6l0w6kgbW9kaWZpw6kgbWFudWVsbGVtZW50LiBMYSBwcmVtacOocmUgb2NjdXJyZW5jZSBkZSAiODQwIiAob3UgIjEyMDAiIHBvdXIgbGUgbm90ZWJvb2spIGVzdCByZW1wbGFjw6llIHBhciAxMjQwIChvdSAiMTYwMCIgcG91ciBsZSBub3RlYm9rKSBwb3VyIG1pZXV4IGNvdXZyaXIgbCdlc3BhY2UgaG9yaXpvbnRhbC4gVm91cyBwb3V2ZXogbW9kaWZpZXIgbGUgQ1NTIG1hbnVlbGxlbWVudCBkYW5zIGxlIGNhcyBvw7kgdm90cmUgZMOpZmluaXRpb24gZCfDqWNyYW4gZXN0IHRyb3AgZmFpYmxlIHBvdXIgMTI0MCAob3UgMTYwMCkgcGl4ZWxzLg0KDQpMJ2ludMOpZ3JhbGl0w6kgZGVzIG1vZMOobGVzIGV0IGRhdGFzZXRzIGludGVybcOpZGlhaXJlcyBzb250IGV4cG9ydMOpZXMgcGFyIGxlIHNjcmlwdC4NCg0KIyMgUHLDqXBhcmF0aW9uDQoNCkF2YW50IGRlIGNvbW1lbmNlciB0b3V0ZSByZWNoZXJjaGUsIG5vdXMgZGV2b25zIGNoYXJnZXIgbGVzIGRvbm7DqWVzIGVuIG3DqW1vaXJlLiBEZSBwbHVzLCBub3VzIGRldm9ucyBtZXR0cmUgbCdlbmNvZGluZyBkZSBSIGVuIFVURi04LCBwdWlzcXUnb24gdXRpbGlzZSBkZXMgY2FyYWN0w6hyZXMgaW50ZXJkaXRzIGF1IGZvcm1hdCBJU08tODg1OS0xLiBDZWxhIHNlIGZhaXQgdG91dCBzaW1wbGVtZW50IGRhbnMgUlN0dWRpbyB2aWEgVG9vbHMgPiBHbG9iYWwgT3B0aW9ucyA+IENvZGUgPiBTYXZpbmcgPiBEZWZhdWx0IFRleHQgRW5jb2RpbmcuDQoNCiMjIyBDaGFyZ2VtZW50IGRlcyBsaWJyYXJpZXMNCg0KTGVzIGluZm9ybWF0aW9ucyBjaS1kZXNzb3VzIG5vdXMgcGVybWV0dHJvbnQgZGUgcmUtY3LDqWVyIGwnZW52aXJvbm5lbWVudCBkYW5zIGxlcXVlbCBvbiB0cmF2YWlsbGUuIFBvdXIgaW5mb3JtYXRpb24sIGxhIGNvbmZpZ3VyYXRpb24gaGFyZHdhcmUgZXN0IGxhIHN1aXZhbnRlIDoNCg0KLSBQcm9jZXNzZXVyIDogSW50ZWwgaTctMzkzMEsgKDEyIHNvY2tldHMgdmlydHVhbGlzw6lzKQ0KLSBSQU0gOiA1NCBHQiAoMTUwIEdCIHN3YXApDQotIENhcnRlIGdyYXBoaXF1ZSA6IGF1Y3VuZQ0KDQpEZSBtw6ptZSwgYXUgbml2ZWF1IHNvZnR3YXJlIDoNCg0KLSBTeXN0w6htZSBkJ2V4cGxvaXRhdGlvbiA6IFdpbmRvd3MgU2VydmVyIDIwMTIgUjINCi0gUjogTWljcm9zb2Z0IFIgQ2xpZW50LCB2ZXJzaW9uIDMuMy4yIGRlIFIgKyBJbnRlbCBNS0wNCi0gUlN0dWRpbyBQcmV2aWV3LCB2ZXJzaW9uIHN1cMOpcmlldXJlIG91IMOpZ2FsZSDDoCAxLjAuMTM2IChyw6lzb2x1dGlvbiBkdSBwcm9ibMOobWUgZCdlbmNvZGluZyBkZXMgY2FyYWN0w6hyZXMgZW4gTWFya2Rvd24pDQotIFJ0b29scyBwb3VyIFIgMy4zLnggcG91ciBsYSBjb21waWxhdGlvbiBkZSBwYWNrYWdlcyBwb3VyIFINCi0gTWluR1cgNi4yLjAgcG91ciBsYSBjb21waWxhdGlvbiBkZSBjb2RlIEMrKw0KLSBKYXZhIDggVXBkYXRlIDExMSBwb3VyIGwndXRpbGlzYXRpb24gZGUgSDJPDQotIEdpdCBCYXNoIHBvdXIgbCd1dGlsaXNhdGlvbiBkZSBCYXNoIChwb3VyIE1pbkdXKQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCiMgTm90ZWJvb2sNCmxpYnJhcnkoa25pdHIpDQpvcHRzX2tuaXQkc2V0KHJvb3QuZGlyID0gIkU6L0F1eGl2aWEvIikgIyBXb3JraW5nIGRpcmVjdG9yeQ0KI29wdHNfY2h1bmskc2V0KHRpZHkgPSBGQUxTRSkgIyBDb2RlIGxpc2libGUgZXQgYcOpcsOpDQpvcHRzX2NodW5rJHNldChyZXN1bHRzID0gImhvbGQiKSAjIE91dHB1dHMgZMOpZsOpcsOpcyBlbiBmaW4gZGUgY2hhcXVlIGNodW5rIGRlIGNvZGUNCiNvcHRpb25zKGZvcm1hdFIuYmxhbmsgPSBUUlVFKSAjIENvbnNlcnZhdGlvbiBkZXMgbGlnbmVzIHZpZGVzIHBvdXIgbGEgbGlzaWJpbGl0w6kNCm9wdHNfY2h1bmskc2V0KGZpZy5hbGlnbiA9ICJjZW50ZXIiKSAjIEFsaWduZW1lbnQgaG9yaXpvbnRhbCBkZXMgZmlndXJlcw0KYGBgDQoNCmBgYHtyIExpYnJhaXJpZXMsIHJlc3VsdHM9ImhpZGUiLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBDaGFyZ2VtZW50IGRlIGRvbm7DqWVzDQpsaWJyYXJ5KGRhdGEudGFibGUpDQoNCiMgUsOpZ3Jlc3Npb24gbGluw6lhaXJlIGVuIEMrKw0KbGlicmFyeShSY3BwQXJtYWRpbGxvKQ0KDQojIGh0bWx3aWRnZXRzIC8gM2RqcyAvIFBsb3RseQ0KbGlicmFyeShwbG90bHkpDQoNCiMgaHRtbHdpZGdldHMgLyBkYXRhdGFibGVzIC8gZm9ybWF0dGFibGUNCmxpYnJhcnkoRFQpDQpsaWJyYXJ5KGZvcm1hdHRhYmxlKQ0KDQojIEdyYXBoaXF1ZSB0YWJsZXBsb3QNCmxpYnJhcnkodGFicGxvdCkNCg0KIyBNYWNoaW5lIExlYXJuaW5nIGfDqW7DqXJhbGlzdGUgZW4gQysrDQpsaWJyYXJ5KHhnYm9vc3QpDQoNCiMgTWFjaGluZSBMZWFybmluZyBnw6luw6lyYWxpc3RlIGVuIEphdmENCmxpYnJhcnkoaDJvKQ0KbG9jYWxIMk8gPSBoMm8uaW5pdChudGhyZWFkcyA9IDEsIG1heF9tZW1fc2l6ZSA9ICI0RyIpICMgMSBUaHJlYWQgKHJlcHJvZHVjdGlvbiBkZXMgcsOpc3VsdGF0cyksIDRHQiBkZSBtw6ltb2lyZSB2aXZlDQpoMm8ubm9fcHJvZ3Jlc3MoKSAjIENhY2hlIGxhIGJhcnJlIGRlIHByb2dyZXNzaW9uDQoNCiMgT3B0aW1pc2V1ciBjb250aW51IGV0IGRpc2NyZXQNCmxpYnJhcnkoQ0VvcHRpbSkNCg0KIyBUaW1pbmcNCmxpYnJhcnkoUi51dGlscykNCg0KIyBQbGVpbiBkJ3V0aWxpdGFpcmVzDQpsaWJyYXJ5KExhdXJhZSkNCg0KIyBGb25jdGlvbiBkZSB0aW1pbmcgOiBwcmVuZCB1biB0ZW1wcyBwYXNzw6kgZXQgdW5lIHTDomNoZSwgYWZmaWNoZSBsYSBkaWZmw6lyZW5jZSBkZSB0ZW1wcyBhdSBjZW50acOobWUgZGUgc2Vjb25kZQ0KdGltaW5nIDwtIGZ1bmN0aW9uKHRpbWVyLCB3aGF0ID0gIj8iKSB7DQogIGNhdCgiVGVtcHMgZCdleMOpY3V0aW9uIGRlIGxhIHTDomNoZSAnIiwgd2hhdCwgIicgOiAiLCBzcHJpbnRmKCIlLjAyZiIsICgoU3lzdGVtJGN1cnJlbnRUaW1lTWlsbGlzKCkgLSB0aW1lcikgLyAxMDAwKSksICIgc2Vjb25kZXMuICBcbiIsIHNlcCA9ICIiKSAjIERvdWJsZSBlc3BhY2UgcG91ciDDqXZpdGVyIGxlIGJ1ZyBkJ2VzcGFjZSBlbiBNYXJrZG93biBSDQp9DQoNCiMgSW5mb3JtYXRpb25zIHN1ciBsYSBzZXNzaW9uDQpzZXNzaW9uSW5mbygpDQpgYGANCg0KIyMjIENoYXJnZW1lbnQgZGVzIGRvbm7DqWVzDQoNClBvdXIgY2hhcmdlciBsZXMgZG9ubsOpZXMsIHF1aSBzZSB0cm91dmVudCBkZSBtYW5pw6hyZSBzw6lwYXLDqWUgZGFucyBkZXMgZmljaGllcnMsIG5vdXMgZGV2b25zIGxlcyBvdXZyaXIgdW4gcGFyIHVuLiBDZWxhIHNlIGZhaXQgZGUgbWFuacOocmUgdHJpdmlhbGUgc3VyIFIgcGFyIHVuZSBib3VjbGU6DQoNCi0gTm91cyBhdm9ucyAzMTQgZmljaGllcnMgbnVtw6lyb3TDqXMgZGUgMSDDoCAzMTQgKGV0IG5vbiBwYXMgMDAxIMOgIDMxNCkgc291cyBsYSBmb3JtZSBNb3ZlbWVudEFBTF9SU1NfWC5jc3YgKHJlbXBsYWNlciBYKSBkYW5zIGxlIGRvc3NpZXIgL2RhdGFzZXQsIGRvbnQgbGEgcHJlbWnDqHJlIGNvbG9ubmUgcG9zc8OoZGUgbGUgY2FyYWN0w6hyZSBkacOoc2UgZW4gdHJvcCAocG91ciBtYXJxdWVyIGxlcyBjb2xvbm5lcykgcXVpIGRvaXQgw6p0cmUgZW5sZXbDqWUNCi0gTm91cyBhdm9ucyBsZXMgbGFiZWxzIGRhbnMgbGUgZmljaGllciBub21tw6kgTW92ZW1lbnRBQUxfdGFyZ2V0LmNzdiBkYW5zIGxlIGRvc3NpZXIgL2RhdGFzZXQNCi0gTGVzIGRvbm7DqWVzIGFubmV4ZXMgZGUgZmFtaWxsZSBkZSBtb3V2ZW1lbnRzIChNb3ZlbWVudEFBTF9EYXRhc2V0R3JvdXAuY3N2KSBldCBkZSBjaGVtaW5zIChNb3ZlbWVudEFBTF9QYXRocy5jc3YpIHNvbnQgZGFucyBsZSBkb3NzaWVyIC9ncm91cA0KDQpgYGB7ciBDaGFyZ2VtZW50RGF0YX0NCiMgQ29tcHRldXIgZGUgdGVtcHMNCkN1cnJlbnRUaW1lIDwtIHRpbWVyKCkgIyBDaHVuayBDaGFyZ2VtZW50IGRlcyBkb25uw6llcyBlbiBtw6ltb2lyZQ0KDQojIFByw6ktaW5pdGlhbGlzYXRpb24gZGVzIHZhcmlhYmxlcw0KZGF0YV9wcmUgPC0gbGlzdCgpDQoNCiMgQ2hhcmdlbWVudCBkZXMgZG9ubsOpZXMNCmZvciAoaSBpbiAxOjMxNCkgew0KICBkYXRhX3ByZVtbaV1dIDwtIGZyZWFkKHBhc3RlMCgiZGF0YXNldC9Nb3ZlbWVudEFBTF9SU1NfIiwgaSwgIi5jc3YiKSwgc2VwID0gIiwiLCB2ZXJib3NlID0gRkFMU0UsIHNob3dQcm9ncmVzcyA9IEZBTFNFLCBjb2wubmFtZXMgPSBjKCJSU1NfYW5jaG9yMSIsICJSU1NfYW5jaG9yMiIsICJSU1NfYW5jaG9yMyIsICJSU1NfYW5jaG9yNCIpKQ0KfQ0KDQojIENoYXJnZW1lbnQgZGVzIGRvbm7DqWVzIGFubmV4ZXMNCmxhYmVscyA8LSBmcmVhZCgiZGF0YXNldC9Nb3ZlbWVudEFBTF90YXJnZXQuY3N2Iiwgc2VwID0gIiwiLCB2ZXJib3NlID0gRkFMU0UsIHNob3dQcm9ncmVzcyA9IEZBTFNFLCBjb2wubmFtZXMgPSBjKCJzZXF1ZW5jZV9JRCIsICJjbGFzc19sYWJlbCIpKQ0KbGFiZWxzJGNsYXNzX2xhYmVsW2xhYmVscyRjbGFzc19sYWJlbCA9PSAtMV0gPC0gMA0KZ3JvdXBfcm9vbSA8LSBmcmVhZCgiZ3JvdXBzL01vdmVtZW50QUFMX0RhdGFzZXRHcm91cC5jc3YiLCBzZXAgPSAiLCIsIHZlcmJvc2UgPSBGQUxTRSwgc2hvd1Byb2dyZXNzID0gRkFMU0UsIGNvbC5uYW1lcyA9IGMoInNlcXVlbmNlX0lEIiwgImRhdGFzZXRfSUQiKSkNCmdyb3VwX3BhdGggPC0gZnJlYWQoImdyb3Vwcy9Nb3ZlbWVudEFBTF9QYXRocy5jc3YiLCBzZXAgPSAiLCIsIHZlcmJvc2UgPSBGQUxTRSwgc2hvd1Byb2dyZXNzID0gRkFMU0UsIGNvbC5uYW1lcyA9IGMoInNlcXVlbmNlX0lEIiwgInBhdGhfSUQiKSkNCg0KIyBUZW1wcyBuw6ljZXNzYWlyZQ0KdGltaW5nKEN1cnJlbnRUaW1lLCAiQ2hhcmdlbWVudCBkZXMgZG9ubsOpZXMgZW4gbcOpbW9pcmUiKQ0KYGBgDQoNCiMjIFByZW1pw6hyZSBBbmFseXNlIEV4cGxvcmF0b2lyZQ0KDQojIyMgVGVudGF0aXZlIGRlIGNvbXByw6loZW5zaW9uIGR1IHByb2Jsw6htZSA6IHByb2Jsw6htZSBpbmjDqXJlbnQgYXV4IGRvbm7DqWVzDQoNClBvdXIgdGVudGVyIGRlIGNvbXByZW5kcmUgbGUgcHJvYmzDqG1lLCBvbiB2YSBwcmVuZHJlIGxlcyAxNiBkZXJuaWVycyBwb2ludHMgZGUgY2hhcXVlIHPDqXJpZSB0ZW1wb3JlbGxlLCBldCBsZXMgYWZmaWNoZXIgdmlhIDNkanMgdmlhIGwnaW50ZXJmYWNlIFBsb3RseS4NCg0KSWwgZXN0IGNsYWlyIGV0IG5ldCBxdWUgbGEgU2FsbGUgMSBkw6ltb250cmUgdW4gY29tcG9ydGVtZW50IGlycsOpZ3VsaWVyIHBhciByYXBwb3J0IGF1eCBkZXV4IGF1dHJlcyBzYWxsZXMuIFVuZSBhbmFseXNlIHRyYWplY3RvaXJlIHBhciB0cmFqZWN0b2lyZSBtb250cmUgcXVlIGxhIDoNCg0KKiBUcmFqZWN0b2lyZSAxIDogY2FuYWwgb3Bwb3PDqSBzdXIgbCdhbmNyZSAxLCAyLCAzLCBldCA0DQoqIFRyYWplY3RvaXJlIDIgOiBjYW5hbCBvcHBvc8OpIHN1ciBsJ2FuY3JlIDEsIDIsIDQsIGF2ZWMgdW4gZ2FpbiBmYWlibGUgc3VyIGwnYW5jcmUgMw0KKiBUcmFqZWN0b2lyZSAzIDogaW5leGlzdGFuY2UgKGNvbW1lIHByw6l2dSwgZCdhcHLDqHMgbGVzIMOpbm9uY8OpcyAtIHJlcHLDqXNlbnTDqSBwYXIgdW5lIGxpZ25lIGJsZXVlIGZpeMOpZSDDoCAwKQ0KKiBUcmFqZWN0b2lyZSA0IDogY2FuYWwgb3Bwb3PDqSBzdXIgbCdhbmNyZSAxLCAyLCA0LCBhdmVjIHVuIGdhaW4gZm9ydCBzdXIgbCdhbmNyZSAzDQoqIFRyYWplY3RvaXJlIDUgOiBjYW5hbCBvcHBvc8OpIHN1ciBsJ2FuY3JlIDEsIDIsIDMsIGV0IDQNCiogVHJhamVjdG9pcmUgNiA6IGNhbmFsIG9wcG9zw6kgc3VyIGwnYW5jcmUgMSwgMiwgMywgZXQgNA0KDQpJbCBmYXVkcmEgb2JsaWdhdG9pcmVtZW50IHLDqWFsaXNlciB1bmUgdHJhbnNmb3JtYXRpb24gc3VyIGxlcyBkb25uw6llcyBkZSBsYSBzYWxsZSAxIGFmaW4gcXVlIGxlcyBtb2TDqGxlcyBwdWlzc2VudCDDqnRyZSDDqXZhbHXDqXMgZGUgbWFuacOocmUgZmlhYmxlLiBJbCBlc3QgdG91dCDDoCBmYWl0IHBvc3NpYmxlIHF1ZSBsb3JzIGRlIGxhIGNvbGxlY3RlIGRlcyBkb25uw6llcywgbGVzIHNhbGxlcyAxIGV0IDMgb250IMOpdMOpIGludmVyc8OpZXMsIHF1ZSBkZXMgYW5jcmVzIG9udCDDqXTDqSBtYWwgcG9zaXRpb25uw6llcywgb3UgZW5jb3JlIHF1J2lsIHkgYSBldSB1bmUgZXJyZXVyIGRlIG1hbmlwdWxhdGlvbiBkZXMgZG9ubsOpZXMgcG91ciBjcsOpZXIgbGVzIGRhdGFzZXRzIHBhciBzYWxsZS4gVnUgbGUgcHJvYmzDqG1lLCBhdWN1bmUgZGVzIHRyb2lzIGh5cG90aMOoc2VzIG4nZXN0IHZhbGlkw6llIG5pIGV4Y2x1ZS4NCg0KRW4gcsOpYWxpdMOpLCBsJ2ltYWdlIHN1aXZhbnRlLCBkJ2FwcsOocyBsYSByZWNoZXJjaGUgKkFuIGV4cGVyaW1lbnRhbCBjaGFyYWN0ZXJpemF0aW9uIG9mIHJlc2Vydm9pciBjb21wdXRpbmcgaW4gYW1iaWVudCBhc3Npc3RlZCBsaXZpbmcgYXBwbGljYXRpb25zIChCYWNjaXUgZXQgYWwuLDIwMTIpKiBleHBsaXF1ZSBsYSByYWlzb24gZGUgbGEgZGlzcGFyaXTDqSBlbnRyZSBsYSBzYWxsZSAxIGV0IGxlcyBkZXV4IGF1dHJlcyBzYWxsZXMhDQoNCiFbUmVjaGVyY2hlIGRlIEJhY2NpdSBldCBhbC5dKGJhY2NpdTIwMTMuanBnKQ0KDQpJbCBzZXJhaXQgaW50w6lyZXNzYW50IGRlIHRyYXZhaWxsZXIgw6AgbGEgZm9pcyBzdXIgOg0KDQotIEwnaW52ZXJzaW9uIGRlcyBhbmNyZXMgQTEvQTMgZXQgQTIvQTQNCi0gTGEgdGVudGF0aXZlIGRlIHJlcHJvZHVjdGlvbiBkZXMgcsOpc3VsdGF0cyBkZXMgcXVhdHJlIGFuY3Jlcw0KDQpgYGB7ciBBZ3JlZ2F0aW9uUHJvYmxlbWV9DQojIENvbXB0ZXVyIGRlIHRlbXBzDQpDdXJyZW50VGltZSA8LSB0aW1lcigpICMgQ2h1bmsgQWdyw6lnYXRpb24gZGVzIGRvbm7DqWVzDQoNCiMgUHLDqS1pbml0aWFsaXNhdGlvbiBkZSBsYSBzw6lyaWUgdGVtcG9yZWxsZSBkw6lwaXZvdMOpZSBwYXIgbGUgbGFiZWwNCiMgDQojIEFyY2hpY3RlY3R1cmUgZGUgbGEgbWF0cmljZSAoZMOpcGl2b3TDqWUgc2FucyBsYSBzYWxsZSkgMzg0eDQgKDY0IG9ic2VydmF0aW9ucyBwYXIgbGFiZWwsIDE2IG9ic2VydmF0aW9ucyBwYXIgYW5jcmUgcGFyIGxhYmVsKSA6DQojIElEICBTdHJlbmd0aCAgICBBbmNob3IgICAgICBMYWJlbCAgICAgICBUaW1lDQojICAxICAgIHZhbF9hMSAgICAgICAgIDEgICAgICAgICAgMSAgICAgICAgICAxDQojICAyICAgIHZhbF9hMiAgICAgICAgIDEgICAgICAgICAgMSAgICAgICAgICAyDQojICAuLi4NCiMgMTYgICB2YWxfYTE2ICAgICAgICAgMSAgICAgICAgICAxICAgICAgICAgMTYNCiMgMTcgICB2YWxfYTE3ICAgICAgICAgMiAgICAgICAgICAxICAgICAgICAgIDENCiMgIC4uLg0KIyAzMiAgIHZhbF9hMzIgICAgICAgICAyICAgICAgICAgIDEgICAgICAgICAxNg0KIyAzMyAgIHZhbF9hMzMgICAgICAgICAzICAgICAgICAgIDEgICAgICAgICAgMQ0KIyAgLi4uDQojIDQ4ICAgdmFsX2E0OCAgICAgICAgIDMgICAgICAgICAgMSAgICAgICAgIDE2DQojIDQ5ICAgdmFsX2E0OSAgICAgICAgIDQgICAgICAgICAgMSAgICAgICAgICAxDQojICAuLi4NCiMgNjQgICB2YWxfYTE2ICAgICAgICAgNCAgICAgICAgICAxICAgICAgICAgMTYNCiMgNjUgICB2YWxfYTE3ICAgICAgICAgMSAgICAgICAgICAyICAgICAgICAgIDENCiMgIC4uLg0KIyBJRChhbmNob3IsIGxhYmVsLCB0aW1lKSA9ICgobGFiZWwgLSAxKSAqIDY0KSArICgoYW5jaG9yIC0gMSkgKiAxNikgKyB0aW1lDQphdmdfc2VyaWVzIDwtIGRhdGEudGFibGUobWF0cml4KHJlcCgwLCAxNiAqIDQgKiA2ICogNiksIG5yb3cgPSAxNiAqIDQgKiA2LCBuY29sID0gNCkpDQpjb2xuYW1lcyhhdmdfc2VyaWVzKSA8LSBjKCJTdHJlbmd0aCIsICJBbmNob3IiLCAiTGFiZWwiLCAiVGltZSIpDQoNCiMgUHLDqS1maWxsaW5nIGRlcyBmYWN0ZXVycyAoQW5jaG9yLCBMYWJlbCwgVGltZSkNCmF2Z19zZXJpZXNbWyJBbmNob3IiXV0gPC0gYXMuZmFjdG9yKHJlcChpbnZlcnNlLnJsZShsaXN0KGxlbmd0aHMgPSByZXAoMTYsIDQpLCB2YWx1ZXMgPSAxOjQpKSwgNikpDQpsZXZlbHMoYXZnX3Nlcmllc1tbIkFuY2hvciJdXSkgPC0gcGFzdGUoIkFuY3JlIiwgMTo0LCBzZXAgPSAiIikNCmF2Z19zZXJpZXNbWyJMYWJlbCJdXSA8LSBhcy5mYWN0b3IoaW52ZXJzZS5ybGUobGlzdChsZW5ndGhzID0gcmVwKDE2ICogNCwgNiksIHZhbHVlcyA9IDE6NikpKQ0KbGV2ZWxzKGF2Z19zZXJpZXNbWyJMYWJlbCJdXSkgPC0gcGFzdGUoIlRyYWplY3RvaXJlIiwgMTo2LCBzZXAgPSAiIikNCmF2Z19zZXJpZXNbWyJUaW1lIl1dIDwtIHJlcCgxOjE2LCA2ICogNCkNCg0KIyBUcmFuc2Zvcm1hdGlvbiBlbiBsaXN0ZSBwYXIgc2FsbGUgw6AgZMOpcGl2b3RlciBwYXIgbGEgc3VpdGUNCmF2Z19zZXJpZXMgPC0gbGlzdChjYmluZChhdmdfc2VyaWVzLCBSb29tID0gcmVwKDEsIDM4NCkpLA0KICAgICAgICAgICAgICAgICAgIGNiaW5kKGF2Z19zZXJpZXMsIFJvb20gPSByZXAoMiwgMzg0KSksDQogICAgICAgICAgICAgICAgICAgY2JpbmQoYXZnX3NlcmllcywgUm9vbSA9IHJlcCgzLCAzODQpKSkNCg0KIyBQcsOpLWNvbXB0ZSBkdSBub21icmUgZCdvY2N1cnJlbmNlIGRlcyBsYWJlbHMNCmxhYmVsX2NvdW50IDwtIGxpc3QodGFidWxhdGUoZ3JvdXBfcGF0aFtbInBhdGhfSUQiXV1bZ3JvdXBfcm9vbVtbImRhdGFzZXRfSUQiXV0gPT0gMV0pLA0KICAgICAgICAgICAgICAgICAgICB0YWJ1bGF0ZShncm91cF9wYXRoW1sicGF0aF9JRCJdXVtncm91cF9yb29tW1siZGF0YXNldF9JRCJdXSA9PSAyXSksDQogICAgICAgICAgICAgICAgICAgIHRhYnVsYXRlKGdyb3VwX3BhdGhbWyJwYXRoX0lEIl1dW2dyb3VwX3Jvb21bWyJkYXRhc2V0X0lEIl1dID09IDNdKSkNCg0KIyBEw6l0ZXJtaW5hdGlvbiBkZXMgMTYgZGVybmnDqHJlcyBvYnNlcnZhdGlvbnMgKDIgc2Vjb25kZXMgw6AgOCBIeiksIG1veWVubmlzw6llcw0KZm9yIChpIGluIDE6MzE0KSB7DQogIHRlbXBfbGFiZWwgPC0gKChncm91cF9wYXRoW1sicGF0aF9JRCJdXVtpXSAtIDEpICogNjQpICsgMSAjIExpZ25lIGRlIGTDqW1hcnJhZ2UgZGFucyBsYSBtYXRyaWNlIGFncsOpZ8OpZQ0KICB0ZW1wX29icyA8LSBucm93KGRhdGFfcHJlW1tpXV0pIC0gMTUgIyBMaWduZSBkZSBkw6ltYXJyYWdlIGRhbnMgbGEgbWF0cmljZSDDoCBzYXV2ZWdhcmRlcg0KICANCiAgYXZnX3Nlcmllc1tbZ3JvdXBfcm9vbVtbImRhdGFzZXRfSUQiXV1baV1dXVt0ZW1wX2xhYmVsOih0ZW1wX2xhYmVsICsgNjMpLCAxXSA8LSBhdmdfc2VyaWVzW1tncm91cF9yb29tW1siZGF0YXNldF9JRCJdXVtpXV1dW3RlbXBfbGFiZWw6KHRlbXBfbGFiZWwgKyA2MyksIDFdICsgKHVubGlzdChkYXRhX3ByZVtbaV1dW3RlbXBfb2JzOih0ZW1wX29icyArIDE1KSwgXSkgLyBsYWJlbF9jb3VudFtbZ3JvdXBfcm9vbVtbImRhdGFzZXRfSUQiXV1baV1dXVtncm91cF9wYXRoW1sicGF0aF9JRCJdXVtpXV0pDQp9DQoNCiMgRMOpcGl2b3RhZ2UgZGUgbGEgdmFyaWFibGUgZMOpZmluaXNzYW50IGxhIHNhbGxlDQphdmdfc2VyaWVzIDwtIHJiaW5kKGF2Z19zZXJpZXNbWzFdXSwgYXZnX3Nlcmllc1tbMl1dLCBhdmdfc2VyaWVzW1szXV0pDQphdmdfc2VyaWVzW1siUm9vbSJdXSA8LSBhcy5mYWN0b3IoaW52ZXJzZS5ybGUobGlzdChsZW5ndGhzID0gcmVwKDM4NCwgMyksIHZhbHVlcyA9IDE6MykpKQ0KbGV2ZWxzKGF2Z19zZXJpZXNbWyJSb29tIl1dKSA8LSBwYXN0ZSgiU2FsbGUiLCAxOjMsIHNlcCA9ICIiKQ0KDQojIEFmZmljaGFnZSBzb3VzIGZvcm1lIGRlIHBsb3QgaW50ZXJhY3RpZiBkZSBtYW5pw6hyZSBhdXRvbWF0aXPDqWUNCmdncGxvdGx5KGdncGxvdChkYXRhID0gYXZnX3NlcmllcywgYWVzX3N0cmluZyh4ID0gIlRpbWUiLCB5ID0gIlN0cmVuZ3RoIiwgZ3JvdXAgPSAiTGFiZWwiLCBjb2xvciA9ICJMYWJlbCIpKSArIGdlb21fbGluZSgpICsgZ2VvbV9wb2ludCgpICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MiIpICsgdGhlbWVfYncoKSArIGZhY2V0X2dyaWQoQW5jaG9yIH4gUm9vbSkgKyBsYWJzKHRpdGxlID0gIkV2b2x1dGlvbiBkZSBsYSBmb3JjZSBkdSBzaWduYWwgZGUgbCdhbmNyZSBwYXIgcmFwcG9ydCBhdSB0ZW1wcyIpLCB3aWR0aCA9IDk2MCwgaGVpZ2h0ID0gNzIwKQ0KDQojIEVucmVnaXN0cmVtZW50IGRlIGxhIHRhYmxlIHBvdXIgdXNhZ2UgdWx0w6lyaWV1ciBzaSBuw6ljZXNzYWlyZQ0KZndyaXRlKGF2Z19zZXJpZXMsICJhZ3JlZ2F0aW9uL2FncmVnYXRpb24xLmNzdiIpDQoNCiMgVGVtcHMgbsOpY2Vzc2FpcmUNCnRpbWluZyhDdXJyZW50VGltZSwgIkFncsOpZ2F0aW9uIGRlcyBkb25uw6llcyIpDQpgYGANCg0KIyMjIENyw6lhdGlvbiBkZSBmZWF0dXJlcw0KDQpQb3VyIGNyw6llciB1biBiZW5jaG1hcmsgcmFwaWRlIGJhc8OpIHN1ciBsZXMgZG9ubsOpZXMgZXQgcydhY2NvbW9kZXIgZGVzIHRhaWxsZXMgZGlmZsOpcmVudGVzIGRlcyB2YXJpYWJsZXMsIG9uIGNyw6nDqSBsZXMgZGlmZsOpcmVudGVzICgzNikgZmVhdHVyZXMgOg0KDQotIENvZWZmaWNpZW50IGxpbsOpYWlyZXMgbXVsdGlwbGljYXRldXJzIHBhciBtb2TDqGxlIGxpbsOpYWlyZSBlbnRyZSBjaGFxdWUgYW5jcmUgKHRyb2lzIGFuY3JlcyBlc3RpbWVudCB1bmUgYW5jcmUpICgxNiBmZWF0dXJlcykgPT4gY2VzIGZlYXR1cmVzIGRvaXZlbnQgcG91dm9pciByw6lwb25kcmUgw6AgbGEgZGlyZWN0aW9uIGxpbsOpYWlyZSBwcm9iYWJsZSBkZSBsYSBwZXJzb25uZSwgcGFyIGFuY3JlDQotIFLDqXNpZHVzIGRlcyBtb2TDqGxlcyBsaW7DqWFpcmVzICgxNiBmZWF0dXJlcykgPT4gY2VzIGZlYXR1cmVzIGRvaXZlbnQgYW5ub25jZXIgbCdpbmNlcnRpdHVkZSBsaW7DqWFpcmUgZGUgbGEgcGVyc29ubmUsIHBhciBhbmNyZQ0KLSBMZSBkZXJuaWVyIHBvaW50IGF2YW50IGQnb2JzZXJ2YXRpb24gZW5yZWdpc3Ryw6kgKDQgZmVhdHVyZXMpID0+IGNlcyBmZWF0dXJlcyBkb2l2ZW50IHBlcm1ldHRyZSBkZSBwb3NpdGlvbm5lciBsYSBwZXJzb25uZSBqdXN0ZSBhdmFudCBzYSB0cmFqZWN0b2lyZSBmdXR1cmUNCg0KT24gw6l2aXRlcmEgZGUgdGVudGVyIGRlIHByw6lkaXJlIGxlIG1vdXZlbWVudCBkJ3VuZSBwZXJzb25uZSBhdSBuaXZlYXUgbG9jYWwgOCBzZWNvbmRlcyBwbHVzIHRhcmQsIHB1aXNxdSdvbiBuZSBkaXNwb3NlIHBhcyBmb3Jjw6ltZW50IGQndW4gamV1IGRlIGRvbm7DqWVzIGxhcmdlIHBhciBzw6lyaWUgdGVtcG9yZWxsZSAoc2FucyBvdWJsaWVyIHF1ZSBsZXMgcXVhdHJlIGFuY3JlcyBzb250IGTDqXBlbmRlbnRlcyBsJ3VuZSBkZSBsJ2F1dHJlLCBkb25jIHByw6lkaXJlIGwndW5lIG7DqWNlc3NpdGUgZGUgcHLDqWRpcmUgbGVzIHRyb2lzIGF1dHJlcyDDqWdhbGVtZW50IGV0IHNpbXVsdGFuw6ltZW50IHNpIHBvc3NpYmxlKS4NCg0KYGBge3IgQ3JlYXRpb25GZWF0dXJlMX0NCiMgQ29tcHRldXIgZGUgdGVtcHMNCkN1cnJlbnRUaW1lIDwtIHRpbWVyKCkgIyBDaHVuayBDcsOpYXRpb24gZGVzIGZlYXR1cmVzDQoNCiMgUHLDqS1pbml0aWFsaXNhdGlvbiBkZSBsYSBmcmFtZQ0KbWluaV9sbSA8LSBkYXRhLmZyYW1lKG1hdHJpeChucm93ID0gMzE0LCBuY29sID0gMzYpKQ0KDQojIEJvdWNsZSBwYXIgc8OpcmllIHRlbXBvcmVsbGUNCmZvciAoaSBpbiAxOjMxNCkgew0KICANCiAgIyBCb3VjbGUgcGFyIGFuY3JlDQogIGZvciAoaiBpbiAxOjQpIHsNCiAgICANCiAgICAjIEVudHJhaW5lbWVudCBkJ3VuIG1vZMOobGUgbGluw6lhaXJlIHV0aWxpc2FudCBsZXMgYXV0cmVzIGFuY3JlcywgYXZlYyBsJ2ludGVyY2VwdHJpY2UNCiAgICB0ZW1wX21vZGVsIDwtIGZhc3RMbVB1cmUoWCA9IGNiaW5kKGFzLm1hdHJpeChkYXRhX3ByZVtbaV1dWywgKDE6NClbLWpdLCB3aXRoID0gRkFMU0VdKSwgcmVwKDEsIG5yb3coZGF0YV9wcmVbW2ldXSkpKSwgeSA9IGRhdGFfcHJlW1tpXV1bW2pdXSkNCiAgICANCiAgICAjIEVucmVnaXN0cmVtZW50IGRlcyBjb2VmZmljaWVudHMgZXQgZGVzIHLDqXNpZHVzDQogICAgbWluaV9sbVtpLCAoaiAqIDggLSA3KTooaiAqIDgpXSA8LSBjKHRlbXBfbW9kZWwkY29lZmZpY2llbnRzLCB0ZW1wX21vZGVsJHN0ZGVycikNCiAgICANCiAgfQ0KICANCiAgIyBBam91dCBkdSBkZXJuaWVyIMOpbMOpbWVudCBkZSBsYSBzw6lyaWUgdGVtcG9yZWxsZSAoNCBhbmNyZXMpDQogIG1pbmlfbG1baSwgMzM6MzZdIDwtIGRhdGFfcHJlW1tpXV1bbnJvdyhkYXRhX3ByZVtbaV1dKSwgXQ0KICANCn0NCg0KIyBFbnJlZ2lzdHJlbWVudCBkZXMgZG9ubsOpZXMgYXUgZm9ybWF0IENTVg0KZndyaXRlKGNiaW5kKG1pbmlfbG0sIEdyb3VwID0gZ3JvdXBfcm9vbVtbImRhdGFzZXRfSUQiXV0sIExhYmVsID0gZ3JvdXBfcGF0aFtbInBhdGhfSUQiXV0pLCAiZmVhdHVyZXMvZmVhdHVyZXMxLmNzdiIpDQoNCiMgVGVtcHMgbsOpY2Vzc2FpcmUNCnRpbWluZyhDdXJyZW50VGltZSwgIkNyw6lhdGlvbiBkZXMgZmVhdHVyZXMiKQ0KYGBgDQoNCiMjIyBGZWF0dXJlIE1hcA0KDQpPbiBlbnJlZ2lzdHJlIGxlIG5vbSBkZXMgZmVhdHVyZXMgZGFucyB1biBmaWNoaWVyIMOgIHBhcnQgKEZlYXR1cmUgbWFwID0gIlZlY3RldXIgZGUgZG9ubsOpZXMgPT4gRXNwYWNlIGRlIGZlYXR1cmVzIiwgaWNpIHVuZSBjb2xvbm5lID0gdW5lIGZlYXR1cmUpLiBJbCBlc3QgdXRpbGUgZGUgZmFpcmUgY29ycmVzcG9uZHJlIGRlcyBjb2xvbm5lcyDDoCBkZXMgdmFyaWFibGVzLCBzdXJ0b3V0IGxvcnNxdWUgcGFyZm9pcyBvbiB1dGlsaXNlIGRlcyB0ZWNobmlxdWVzIGRlIHJlbWFwcGluZyAoZXhlbXBsZSA6IFtrZXJuZWwgdHJpY2tdKGh0dHBzOi8vbWVkaXVtLmNvbS9ATGF1cmFlMi91c2luZy15b3VyLWJyYWluLWZvci1zbWFydC10cmFuc2Zvcm1hdGlvbnMtb2YtZGF0YS04ZmE4NTNiY2YzNTAjLmNkeXE1dGJ0NykpLg0KDQpgYGB7ciBGZWF0dXJlTWFwfQ0KY2F0KGMocGFzdGUwKHJlcChjKHBhc3RlMCgiQ29lZiIsIDE6NCksIHBhc3RlMCgiUsOpc2kiLCAxOjQpKSwgNCksIHBhc3RlMCgiXyIsIGludmVyc2UucmxlKGxpc3QobGVuZ3RocyA9IHJlcCg4LCA0KSwgdmFsdWVzID0gMTo0KSkpKSwgcGFzdGUwKCJQb3NJbml0aWFsZV8iLCAxOjQpKSwgc2VwID0gIiwgIiwgZmlsZSA9ICJmZWF0dXJlcy9tYXAuY3N2IiwgYXBwZW5kID0gRkFMU0UpDQpgYGANCg0KIyBQcmVtacOocmUgQW5hbHlzZSBTeXN0w6ltaXF1ZQ0KDQpMJ2FuYWx5c2Ugc3lzdMOpbWlxdWUgcG9ydGUgc3VyIGxhIHBlcmZvcm1hbmNlIGRlcyBtb2TDqGxlcyBlbiB1dGlsaXNhbnQgbGV1cnMgcGFyYW3DqHRyZXMgcGFyIGTDqWZhdXQsIGVuIGNvdXZyYW50IHNpIHBvc3NpYmxlIHBsdXNpZXVycyBkaWZmw6lyZW50IHR5cGVzIGRlIG1vZMOobGVzIDoNCg0KYGBge3IgVGFibGVhdU1vZGVsZXMsIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmdzPUZBTFNFLCByZXN1bHRzPSdhc2lzJ30NCiMgUGFzIGQnYXV0cmVzIG1veWVucyBzaW1wbGVzIGNyb3NzLXBsYXRlZm9ybWVzICsgY3Jvc3MtZmljaGllcnMNCnRlbXBfcHJpbnQgPC0gInwgVHlwZSBkZSBtb2TDqGxlIHwgRGVzY3JpcHRpb24gfCBXcmFwcGVyIHwNCnwtLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18Oi0tLS0tLS0tLS0tLS0tLS0tLS06fA0KfCBMaW7DqWFpcmUgfCBSw6lncmVzc2lvbiBsb2dpc3RpcXVlIChvbiB1dGlsaXNlcmEgbGUgbW9kw6hsZSBsaW7DqWFpcmUgZ8OpbsOpcmFsaXPDqSkgfCB4Z2Jvb3N0IChDKyspLCBIMk8gKEphdmEpIHwNCnwgTm9uLWxpbsOpYWlyZSBzYW5zIGVuc2VtYmxlIHwgQXJicmUgZGUgZMOpY2lzaW9uIChvbiB1dGlsaXNlcmEgdW5lIGl0w6lyYXRpb24gZGUgYm9vc3RpbmcgZCdhcmJyZXMgZGUgZMOpY2lzaW9uKSB8IHhnYm9vc3QgKEMrKyksIEgyTyAoSmF2YSkgfA0KfCBOb24tbGluw6lhaXJlIGQndW5kZXJmaXR0aW5nIHwgQXJicmUgZGUgZMOpY2lzaW9uIChvbiB1dGlsaXNlcmEgbGUgbW9kw6hsZSBkZSBSYW5kb20gRm9yZXN0KSB8IHhnYm9vc3QgKEMrKyksIEgyTyAoSmF2YSkgfA0KfCBOb24tbGluw6lhaXJlIGQnb3ZlcmZpdHRpbmcgfCBBcmJyZSBkZSBkw6ljaXNpb24gKG9uIHV0aWxpc2VyYSBsZSBtb2TDqGxlIGQnYXJicmVzIGRlIGTDqWNpc2lvbiBib29zdMOpKSB8IHhnYm9vc3QgKEMrKyksIEgyTyAoSmF2YSkgfA0KfCBMaW7DqWFpcmUgZCdvdmVyZml0dGluZyB8IFLDqXNlYXV4IGRlIG5ldXJvbmVzIChvbiB0ZXN0ZXJhIHVuZSBhcmNoaXRlY3R1cmUgMzJ4NiArIFJlTFUgLyBUYW5oLCAxNngxNng2ICsgUmVMVSAvIFRhbmgpIHwgSDJPIChKYXZhKSB8Ig0KY2F0KHRlbXBfcHJpbnQpICMgQWZmaWNoZSBsYSB0YWJsZSB0ZWwgcXVlbCBhdSBmb3JtYXQgTWFya2Rvd24NCmBgYA0KDQpQYXIgc2ltcGxpY2l0w6ksIG9uIHV0aWxpc2VyYSB4Z2Jvb3N0LCBxdWkgZXN0IHVuIG1vZMOobGUgY29kw6kgZW4gQysrIGRvbnQgbGEgdml0ZXNzZSBkZSBjYWxjdWwgKGV0IGwnZXNzZW50aWVsIGRlIGxhIHBhcmFtw6l0cmlzYXRpb24pIGVzdCBpbsOpZ2Fsw6llIChtaXMgw6AgcGFydCBMaWdodEdCTSBwYXIgTWljcm9zb2Z0KSwgYWluc2kgcXVlIEgyTyBjb2TDqSBlbiBKYXZhIChyZWxhdGl2ZW1lbnQgYmVhdWNvdXAgcGx1cyBsZW50LCBtw6ptZSBzaSBwbHVzIHJhcGlkZSBxdWUgbGEgcGx1cGFydCBkZXMgbGlicmFyaWVzIGRlIG1hY2hpbmUgbGVhcm5pbmcpLiBOb3VzIG5lIHNhdXZlZ2FyZG9ucyBwYXMgZW5jb3JlIGRlIG1vZMOobGVzLiBDZXMgZGV1eCBsaWJyYXJpZXMgZGlzcG9zZW50IGRlIG1vZMOobGVzIGV4cG9ydGFibGVzIHF1aSBwZXV2ZW50IMOqdHJlIGVucmVnaXN0csOpcyBldCBkw6lwbG95w6lzIHJlc3BlY3RpdmVtZW50IGVuIEMvc2hlbGwgKHhnYm9vc3QpIGV0IEphdmEgKEgyTykgcG91ciB1biB1c2FnZSBpbW3DqWRpYXQuDQoNCk5vdXMgbid1dGlsaXNvbnMgcGFzIHVuIEtOTiAodm9pc2lucyBsZXMgcGx1cyBwcm9jaGVzKSBuaSB1biBTVk0gKFN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzKSBwb3VyIGxldXJzIHByb2Jsw6htZXMgZCdlbnRyYWluZW1lbnQgZXQgZGUgZMOpcGxvaWVtZW50IChLTk4gZMOpZsOocmUgbCdlbnRyYWluZW1lbnQgw6AgbGEgcHLDqWRpY3Rpb24sIGV0IFNWTSBhIHVuZSBjb21wbGV4aXTDqSBkZSBjYWxjdWwgdGVsbGUgcXVlIGNlIG5lIHNvbnQgcGFzIGRlcyBtb2TDqGxlcyBkw6lwbG95YWJsZXMgZGUgbWFuacOocmUgZWZmaWNpZW50ZSBlbiBlbnRyZXByaXNlKS4NCg0KTGVzIG9iamVjdGlmcyBzZXJvbnQgbGVzIHN1aXZhbnRzIDoNCg0KLSBNaW5pbWlzZXIgbCdlcnJldXIgZGUgY2xhc3NpZmljYXRpb24NCi0gTmUgcGFzIGTDqXBhc3NlciAyNSUgZGUgZmVhdHVyZXMgKDUyKSBwYXIgcmFwcG9ydCBhdSBub21icmUgZCdvYnNlcnZhdGlvbnMgcGFyIGZvbGQgcG91ciDDqXZpdGVyIGxlIHN1cmFwcHJlbnRpc3NhZ2UgZGVzIGZlYXR1cmVzIChxdWkgbmUgc2UgcmVww6lyZXJhIHBhcyBzdXIgbGVzIG3DqXRyaXF1ZXMgZGUgdmFsaWRhdGlvbiBhdmVjIHNpIHBldSBkJ8OpY2hhbnRpbGxvbnMsIHF1aSBlc3QgbGUgcHJvYmzDqG1lIGRlIGZsdWN0dWF0aW9uIHBhciBlcnJldXIpDQotIETDqXBhc3NlciBsZSBzZXVpbCBkdSBtb2TDqGxlICJhbMOpYXRvaXJlIiAoNTMvMjEwICsgNTMvMjA4ICsgNTIvMjEwLCBzb2l0IGByIHNwcmludGYoIjA1LjAyZiIsIDEwMCAqICg1My8yMTAgKyA1My8yMDggKyA1Mi8yMTApKWAlKQ0KDQpPbiBwcsOpcGFyZSBkJ2Fib3JkIGxlcyBwcsOpLXJlcXVpcyBwb3VyIGxlIGNhbGN1bCwgcXVpIHNvbnQgOg0KDQotIExlcyBmb2xkcyDDoCB1dGlsaXNlciBwb3VyIGxhIGNyb3NzLXZhbGlkYXRpb24gOiBvbiBkw6ljb3VwZXJhIGRlIG1hbmnDqHJlIGRpc2pvaW50ZSBsZXMgc2FsbGVzLCBhZmluIGQnw6l2aXRlciB0b3V0IGxlYWthZ2Ugw6AgbCdpbnTDqXJpZXVyIGRlcyBzYWxsZXMgKGFmaW4gZCdlbXDDqmNoZXIgbGUgbW9kw6hsZSBkZSBtYWNoaW5lIGxlYXJuaW5nIGQnYXBwcmVuZHJlIGxlcyBzYWxsZXMgZXQgbm9uIHBhcyBsYSBnw6luw6lyYWxpc2F0aW9uIMOgIGRlcyDDqWNoYW50aWxsb24gaW5jb25udXMgZGFucyBkZSBub3V2ZWxsZXMgc2FsbGVzKQ0KLSBMYSBtw6l0cmlxdWUgZCfDqXZhbHVhdGlvbiwgb8O5IGljaSBsJ2V4YWN0aXR1ZGUgZGVzIHByw6lkaWN0aW9ucyBlc3QgbXVsdGktY2xhc3NlDQoNCkNvbW1lIG5vdXMgYWxsb25zIGTDqWNvdXBlciBzZWxvbiBkaWZmw6lyZW50ZXMgbcOpdGhvZGVzLCBub3VzIGRldnJvbnMgY3LDqWVyIMOgIGxhIGZvaXMgbGVzIGZvbGQgZGUgdHJhaW4gbWFpcyBhdXNzaSBkZSB0ZXN0IDoNCg0KLSBFbnRyYWluZW1lbnQgc3VyIHVuZSBzYWxsZSwgcHLDqWRpY3Rpb24gc3VyIHVuZSBhdXRyZSBzYWxsZSAoMSBjb250cmUgMSkNCi0gRW50cmFpbmVtZW50IHN1ciB1bmUgc2FsbGUsIHByw6lkaWN0aW9uIHN1ciBkZXV4IGF1dHJlcyBzYWxsZXMgKDEgY29udHJlIDIpDQotIEVudHJhaW5lbWVudCBzdXIgZGV1eCBzYWxsZXMsIHByw6lkaWN0aW9uIHN1ciB1bmUgYXV0cmUgc2FsbGUgKDIgY29udHJlIDEpDQoNCkxhIG1veWVubmUgZ8OpbsOpcmFsZSBzZXJhIGFncsOpZ8OpZSDDoCBwYXJ0aXIgZGVzIHRyb2lzIG1veWVubmVzIGFncsOpZ8OpZXMgKDEgY29udHJlIDEsIDEgY29udHJlIDIsIGV0IDIgY29udHJlIDEpLiBBdSB0b3RhbCwgbm91cyBhdm9ucyAxNDQgbW9kw6hsZXMgKDEyIG1vZMOobGVzLCAxMiBzZXRzIGQnZW50cmFpbmVtZW50L3ZhbGlkYXRpb24pIMOgIHRlc3Rlci4gVnUgbGEgdGFpbGxlIGRlcyBkb25uw6llcyAoMzE0IG9ic2VydmF0aW9ucywgbW9pbnMgZCd1bmUgY2VudGFpbmUgZGUgZmVhdHVyZXMpIGV0IGxhIG3DqW1vaXJlIHZpdmUgZGlzcG9uaWJsZSAocGx1cyBkZSA1MCBHQiksIG9uIHBldXQgc2UgcGVybWV0dHJlIGRlIHByw6ktY3LDqWVyIHRvdXRlcyBsZXMgZGF0YXNldHMgYXUgbGlldSBkZSBsZXMgY3LDqWVyIMOgIGxhIHZvbMOpZS4NCg0KTGUgdGVtcHMgcGVyZHUgw6AgbGEgY3LDqWF0aW9uIGRlcyBkYXRhc2V0cyBpY2kgZXN0IGR1ZSBlbiBncmFuZGUgcGFydGllIGR1ZSDDoCBIMk8sIGRvbnQgbGVzIG9iamV0cyBkb2l2ZW50IMOqdHJlIHRyYWR1aXRzIGVuIEphdmEgw6AgcGFydGlyIGRlIFIuDQoNCkxlcyBmaWNoaWVycyBzb250IGVucmVnaXN0csOpcyBhdmVjIGxhIG5vbWVuY2xhdHVyZSBzdWl2YW50ZSA6DQoNCi0gTkwgcG91ciAiTm8gTGFiZWwiIChkb25uw6llcyBub24gbGFiZWxsaXPDqWVzKQ0KLSBMIHBvdXIgIkxhYmVsIiAoZG9ubsOpZXMgbGFiZWxsaXPDqWVzKQ0KLSAuY3N2IHBvdXIgbGUgZm9ybWF0IENTViAoQ29tbWEtU2VwYXJhdGVkIFZhbHVlcykNCi0gLmRhdGEgcG91ciBsZSBmb3JtYXQgYmluYWlyZSB4Z2Jvb3N0IChMaWdodFNWTSBjb21wcmVzc8OpKQ0KDQpgYGB7ciBHZW5lcmF0aW9uRG9ubmVlczF9DQojIENvbXB0ZXVyIGRlIHRlbXBzDQpDdXJyZW50VGltZSA8LSB0aW1lcigpICMgQ2h1bmsgUHLDqXBhcmF0aW9uIGRlIGwnw6l2YWx1YXRpb24gZHUgbW9kw6hsZQ0KDQojIE/DuSBzYXV2ZWdhcmRlciBsZXMgZmljaGllcnMgPw0KZmlsZV90YWcgPC0gIjFfZGF0YS8iDQoNCiMgSW5pdGlhbGlzYXRpb24gZGUgbGEgdmFyaWFibGUgcXVpIGFjY3VlaWxsZXJhIGxhIHByw6ljaXNpb24NCmFjY3VyYWN5IDwtIGRhdGEuZnJhbWUobWF0cml4KG5yb3cgPSAxNiwgbmNvbCA9IDEzKSkNCmNvbG5hbWVzKGFjY3VyYWN5KSA8LSBjKCJGb2xkIiwgInhnYl9MaW5lYXJNb2RlbCIsICJ4Z2JfRGVjaXNpb25UcmVlIiwgInhnYl9SYW5kb21Gb3Jlc3QiLCAieGdiX0dyYWRpZW50Qm9vc3RpbmciLCAiaDJvX0xpbmVhck1vZGVsIiwgImgyb19EZWNpc2lvblRyZWUiLCAiaDJvX1JhbmRvbUZvcmVzdCIsICJoMm9fR3JhZGllbnRCb29zdGluZyIsICJoMm9fTk5fMzJ4Nl9SZUxVIiwgImgyb19OTl8zMng2X1NvZnQiLCAiaDJvX05OXzE2eDE2eDZfUmVMVSIsICJoMm9fTk5fMTZ4MTZ4Nl9Tb2Z0IikNCmFjY3VyYWN5WywgMV0gPC0gYygiRm9sZF8xdjIiLCAiRm9sZF8xdjMiLCAiRm9sZF8ydjEiLCAiRm9sZF8ydjMiLCAiRm9sZF8zdjEiLCAiRm9sZF8zdjIiLCAiRm9sZF8xdjIzIiwgIkZvbGRfMnYxMyIsICJGb2xkXzN2MTIiLCAiRm9sZF8xMnYzIiwgIkZvbGRfMTN2MiIsICJGb2xkXzIzdjEiLCAiTW95ZW5uZV8xYzEiLCAiTW95ZW5uZV8xYzIiLCAiTW95ZW5uZV8yYzEiLCAiTW95ZW5uZSIpDQoNCiMgSW5pdGlhbGlzYXRpb24gZGVzIGZvbGRzIHBvdXIgbGEgY3Jvc3MtdmFsaWRhdGlvbg0KZm9sZHNfdHJhaW4gPC0gbGlzdCgpDQpmb2xkc190ZXN0IDwtIGxpc3QoKQ0KdHJhaW5pbmdfZGF0YSA8LSBsaXN0KCkNCnRlc3RpbmdfZGF0YSA8LSBsaXN0KCkNCnRyYWluaW5nX3hnYiA8LSBsaXN0KCkNCnRlc3RpbmdfeGdiIDwtIGxpc3QoKQ0KdHJhaW5pbmdfaDJvIDwtIGxpc3QoKQ0KdGVzdGluZ19oMm8gPC0gbGlzdCgpDQpjb21iaW5hdGlvbnNfdHJhaW4gPC0gYyhsaXN0KDEsIDEsIDIsIDIsIDMsIDMpLCBjb21ibigzLCAxLCBzaW1wbGlmeSA9IEZBTFNFKSwgY29tYm4oMywgMiwgc2ltcGxpZnkgPSBGQUxTRSkpDQpjb21iaW5hdGlvbnNfdGVzdCA8LSBjKGxpc3QoMiwgMywgMSwgMywgMSwgMiksIHJldihjb21ibigzLCAyLCBzaW1wbGlmeSA9IEZBTFNFKSksIHJldihjb21ibigzLCAxLCBzaW1wbGlmeSA9IEZBTFNFKSkpDQp0ZW1wX2ZhY3RvcnMgPC0gYXMuZmFjdG9yKGdyb3VwX3BhdGgkcGF0aF9JRCkNCg0KIyBDcsOpYXRpb24gZGVzIGRvbm7DqWVzIGQnZW50cmFpbmVtZW50IGV0IGRlIHZhbGlkYXRpb24NCmZvciAoaSBpbiAxOjEyKSB7DQogIA0KICAjIENyw6lhdGlvbiBkZXMgZm9sZHMgZCdlbnRyYWluZW1lbnQgZXQgZGUgdmFsaWRhdGlvbg0KICBmb2xkc190cmFpbltbaV1dIDwtIHdoaWNoKGdyb3VwX3Jvb21bWyJkYXRhc2V0X0lEIl1dICVpbiUgY29tYmluYXRpb25zX3RyYWluW1tpXV0pDQogIGZvbGRzX3Rlc3RbW2ldXSA8LSB3aGljaChncm91cF9yb29tW1siZGF0YXNldF9JRCJdXSAlaW4lIGNvbWJpbmF0aW9uc190ZXN0W1tpXV0pDQogIA0KICAjIFJlY2hlcmNoZSBldCBzdXBwcmVzc2lvbiBkdSBsYWJlbCAzIGxvcnNxdWUgbGEgc2FsbGUgMSBlc3QgaXNvbMOpZSAoc29pdCBlbiB0cmFpbiBvbiBlbmzDqHZlIGVuIHRlc3QsIHNvaXQgZW4gdGVzdCBvbiBlbmzDqHZlIGVuIHRyYWluKQ0KICBpZiAoKGxlbmd0aChjb21iaW5hdGlvbnNfdHJhaW5bW2ldXSkgPT0gMSkgJiAoY29tYmluYXRpb25zX3RyYWluW1tpXV1bMV0gPT0gMSkpIHsNCiAgICBmb2xkc190ZXN0W1tpXV0gPC0gZm9sZHNfdGVzdFtbaV1dW2dyb3VwX3BhdGgkcGF0aF9JRFtmb2xkc190ZXN0W1tpXV1dICE9IDNdDQogIH0NCiAgaWYgKChsZW5ndGgoY29tYmluYXRpb25zX3Rlc3RbW2ldXSkgPT0gMSkgJiAoY29tYmluYXRpb25zX3Rlc3RbW2ldXVsxXSA9PSAxKSkgew0KICAgIGZvbGRzX3RyYWluW1tpXV0gPC0gZm9sZHNfdHJhaW5bW2ldXVtncm91cF9wYXRoJHBhdGhfSURbZm9sZHNfdHJhaW5bW2ldXV0gIT0gM10NCiAgfQ0KICANCiAgIyBDcsOpYXRpb24gZGVzIGRvbm7DqWVzIGQnZW50cmFpbmVtZW50IGV0IGRlIHZhbGlkYXRpb24NCiAgdHJhaW5pbmdfZGF0YVtbaV1dIDwtIG1pbmlfbG1bZm9sZHNfdHJhaW5bW2ldXSwgXQ0KICB0ZXN0aW5nX2RhdGFbW2ldXSA8LSBtaW5pX2xtW2ZvbGRzX3Rlc3RbW2ldXSwgXQ0KICANCiAgIyBFbnJlZ2lzdHJlbWVudCBkZXMgZG9ubsOpZXMgQ1NWDQogIGZ3cml0ZSh0cmFpbmluZ19kYXRhW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRyYWluTkxfIiwgc3ByaW50ZigiJTAyZCIsIGkpLCAiLmNzdiIpKQ0KICBmd3JpdGUodGVzdGluZ19kYXRhW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRlc3ROTF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuY3N2IikpDQogIA0KICAjIFRyYW5zZm9ybWF0aW9uIGRlcyBkb25uw6llcyBhdSBmb3JtYXQgYXBwcm9wcmnDqSBwb3VyIHhnYm9vc3QNCiAgdHJhaW5pbmdfeGdiW1tpXV0gPC0geGdiLkRNYXRyaXgoZGF0YSA9IGFzLm1hdHJpeCh0cmFpbmluZ19kYXRhW1tpXV0pLCBsYWJlbCA9IGdyb3VwX3BhdGgkcGF0aF9JRFtmb2xkc190cmFpbltbaV1dXSAtIDEpDQogIHRlc3RpbmdfeGdiW1tpXV0gPC0geGdiLkRNYXRyaXgoZGF0YSA9IGFzLm1hdHJpeCh0ZXN0aW5nX2RhdGFbW2ldXSksIGxhYmVsID0gZ3JvdXBfcGF0aCRwYXRoX0lEW2ZvbGRzX3Rlc3RbW2ldXV0gLSAxKQ0KICANCiAgIyBEdW1waW5nIGRlcyBkYXRhc2V0cyBiaW5haXJlcyB4Z2Jvb3N0DQogIHhnYi5ETWF0cml4LnNhdmUodHJhaW5pbmdfeGdiW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRyYWluTF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuZGF0YSIpKQ0KICB4Z2IuRE1hdHJpeC5zYXZlKHRlc3RpbmdfeGdiW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRlc3RMXyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5kYXRhIikpDQogIA0KICAjIFRyYW5zZm9ybWF0aW9uIGRlcyBkb25uw6llcyBhdSBmb3JtYXQgYXBwcm9wcmnDqSBwb3VyIEgyTw0KICB0cmFpbmluZ19oMm9bW2ldXSA8LSBhcy5oMm8oY2JpbmQoTGFiZWwgPSB0ZW1wX2ZhY3RvcnNbZm9sZHNfdHJhaW5bW2ldXV0sIHRyYWluaW5nX2RhdGFbW2ldXSkpDQogIHRlc3RpbmdfaDJvW1tpXV0gPC0gYXMuaDJvKGNiaW5kKExhYmVsID0gdGVtcF9mYWN0b3JzW2ZvbGRzX3Rlc3RbW2ldXV0sIHRlc3RpbmdfZGF0YVtbaV1dKSkNCiAgDQogICMgRW5yZWdpc3RyZW1lbnQgZGVzIGZyYW1lcyBIMk8gKENTViArIExhYmVsKQ0KICBoMm8uZXhwb3J0RmlsZSh0cmFpbmluZ19oMm9bW2ldXSwgcGFzdGUwKGZpbGVfdGFnLCAidHJhaW5MXyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5jc3YiKSwgZm9yY2UgPSBUUlVFKQ0KICBoMm8uZXhwb3J0RmlsZSh0ZXN0aW5nX2gyb1tbaV1dLCBwYXN0ZTAoZmlsZV90YWcsICJ0ZXN0TF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuY3N2IiksIGZvcmNlID0gVFJVRSkNCiAgDQp9DQoNCiMgVGVtcHMgbsOpY2Vzc2FpcmUNCnRpbWluZyhDdXJyZW50VGltZSwgIlByw6lwYXJhdGlvbiBkZSBsJ8OpdmFsdWF0aW9uIGR1IG1vZMOobGUiKQ0KYGBgDQoNCkRhbnMgbGUgY2FzIG/DuSBvbiBzb3VoYWl0ZXJhaXQgZmFpcmUgdW5lIGNsYXNzaWZpY2F0aW9uIGJpbmFpcmUsIHV0aWxpc2VyIHVuIHNldWlsIGR5bmFtaXF1ZSBwb3VyIGTDqWNvdXBlciBsZXMgbGFiZWxzIGVzdCBwbHVzIGFkw6lxdWF0IChwYXIgcmFwcG9ydCDDoCAwLjUwIGNvbW1lIHNldWlsKS4gT24gbmUgcsOpYWxpc2UgcGFzIGRlIGNsYXNzaWZpY2F0aW9uIGJpbmFpcmUgaWNpLCBtYWlzIGNlbGEgcGV1dCB0b3Vqb3VycyBzZXJ2aXIgZGFucyBsZSBmdXR1ci4NCg0KYGBge3IgQmluYXJ5QWNjdXJhY3l9DQojIExlIGJhY2tlbmQgdXRpbGlzw6kgcGFyIHhnYm9vc3QgcG91ciBkw6l0ZXJtaW5lciBsZSBzZXVpbCDDoCB1dGlsaXNlciBwb3VyIHBhcnRhZ2VyIGxlcyBsYWJlbHMgMCBldCAxDQphY2NfZXZhbCA8LSBmdW5jdGlvbihwcmVkLCBkdHJhaW4pIHsNCiAgDQogICMgUsOpY3Vww6lyYXRpb24gZHUgbGFiZWwNCiAgeV90cnVlIDwtIGdldGluZm8oZHRyYWluLCAibGFiZWwiKQ0KICANCiAgIyBDcsOpYXRpb24gZGUgbGEgZGF0YS50YWJsZSB0cmnDqWUgYXZlYyBjb21tZSBjbMOpIHByaW1haXJlIGxhIHByb2JhYmlsaXTDqQ0KICBEVCA8LSBkYXRhLnRhYmxlKHlfdHJ1ZSA9IHlfdHJ1ZSwgeV9wcm9iID0gcHJlZCwga2V5ID0gInlfcHJvYiIpDQogIA0KICAjIFByw6lwYXJhdGlvbiBwb3VyIGxlIG5ldHRveWFnZSBkZXMgZG91YmxvbnMgcG9zdMOpcmlldXJzDQogIGNsZWFuZXIgPC0gIWR1cGxpY2F0ZWQoRFRbLCAieV9wcm9iIl0sIGZyb21MYXN0ID0gVFJVRSkNCiAgDQogICMgUHLDqS1jYWxjdWwgZGUgdmFyaWFibGVzIHNww6ljaWZpcXVlcw0KICBsZW5zIDwtIGxlbmd0aCh5X3RydWUpDQogIG51bXAgPC0gc3VtKHlfdHJ1ZSkNCiAgDQogICMgRMOpdGVybWluYXRpb24gZGVzIHZyYWlzIG7DqWdhdGlmcyBldCBkZXMgdnJhaXMgcG9zaXRpZnMNCiAgRFRbLCB0bl92IDo9IGN1bXN1bSh5X3RydWUgPT0gMCldDQogIERUWywgdHBfdiA6PSBudW1wIC0gY3Vtc3VtKHlfdHJ1ZSA9PSAxKV0NCiAgDQogICMgTmV0dG95YWdlIGRlcyBkb3VibG9ucyBwb3VyIMOpdml0ZXIgbGUgcHJvYmzDqG1lIGQnb3JkcmUgcGFyIGNoYW5jZQ0KICBEVCA8LSBEVFtjbGVhbmVyLCBdDQogIA0KICAjIETDqXRlcm1pbmF0aW9uIGRlIGwnZXhhY3RpdHVkZSBkZXMgZG9ubsOpZXMNCiAgRFRbLCBhY2MgOj0gKHRuX3YgKyB0cF92KSAvIGxlbnNdDQogIA0KICAjIEFubnVsYXRpb24gw6AgesOpcm8gcG91ciB0b3V0ZSBvYnNlcnZhdGlvbiBkb250IGxlIGNhbGN1bCBhYm91dGl0IMOgIHVuZSBlcnJldXINCiAgRFRbLCBhY2MgOj0gaWZlbHNlKCFpcy5maW5pdGUoYWNjKSwgMCwgYWNjKV0NCiAgDQogICMgUmVjaGVyY2hlIGRlIGxhIG1laWxsZXVyZSBleGFjdGl0dWRlDQogIGJlc3Rfcm93IDwtIHdoaWNoLm1heChEVCRhY2MpDQogIGJlc3RfYWNjIDwtIHJvdW5kKDEwMCAqIERUJGFjY1tiZXN0X3Jvd1sxXV0sIGRpZ2l0cyA9IDgpDQogIA0KICAjIFJldG91ciBkZSBsYSBtZWlsbGV1cmUgZXhhY3RpdHVkZQ0KICByZXR1cm4obGlzdChtZXRyaWMgPSAiYWNjIiwgdmFsdWUgPSBiZXN0X2FjYykpDQogIA0KfQ0KYGBgDQoNCiMjIEVudHJhw65uZW1lbnQgZGVzIGRvdXplIG1vZMOobGVzDQoNCkxlIHRlbXBzIGRlIGNhbGN1bCBlc3QgcHJpbmNpcGFsZW1lbnQgZHUgYXV4IG1vZMOobGVzIEgyTyBxdWkgcHJlbm5lbnQgbGEgbWFqb3JpdMOpIGR1IHRlbXBzIGRlIGNhbGN1bCAoZW52aXJvbiAxODAgc2Vjb25kZXMgY29udHJlIHF1ZWxxdWVzIHNlY29uZGVzIHBvdXIgbGVzIDEyeDQgeGdib29zdCkuIENoYXF1ZSBtb2TDqGxlIGVzdCBlbnRyYWluw6kgc3VyIHVuIHNldCBkJ2VudHJhaW5lbWVudCwgZXQgdGVzdMOpIHN1ciB1biBzZXQgZGUgdmFsaWRhdGlvbi4NCg0KUG91ciB1dGlsaXNlciBsZXMgZmljaGllcnMgSmF2YSwgaWwgZmF1dCB1dGlsaXNlciBsZSBmaWNoaWVyIGgyby1nZW5tb2RlbC5qYXIgYWTDqXF1YXQgdHJvdXZhYmxlIGljaSA6IGh0dHA6Ly9tdm5yZXBvc2l0b3J5LmNvbS9hcnRpZmFjdC9haS5oMm8vaDJvLWdlbm1vZGVsDQoNCk5vdGUgOiBsZXMgbW9kw6hsZXMgZW5yZWdpc3Ryw6lzIG5lIHNvbnQgcGFzIGZvcmPDqW1lbnQgbGVzIG1laWxsZXVycywgbWFpcyBjb250aWVubmVudCBsZSBtb2TDqGxlIGZpbmFsIGVudHJhaW7DqSAoYXZlYyBwb3RlbnRpZWxsZW1lbnQgZGUgbCdvdmVyZml0dGluZykuDQoNCmBgYHtyIEVudHJhaW5lbWVudDEsIGNhY2hlPVRSVUV9DQojIENvbXB0ZXVyIGRlIHRlbXBzDQpDdXJyZW50VGltZSA8LSB0aW1lcigpICMgQ3LDqWF0aW9uIGV0IMOpdmFsdWF0aW9uIGRlcyBkb3V6ZSBtb2TDqGxlcyBkZSBiZW5jaG1hcmsNCg0KIyBPw7kgc2F1dmVnYXJkZXIgbGVzIGZpY2hpZXJzID8NCmZpbGVfdGFnIDwtICIxX21vZGVscy8iDQpmaWxlX2gybyA8LSAiMV9tb2RlbHMiDQoNCnhnYl9keW5hbWljX3RyYWluIDwtIGZ1bmN0aW9uKHRyYWluLCB0ZXN0LCBib29zdGVyLCBucm91bmRzLCBudW1fcGFyYWxsZWxfdHJlZXMpIHsNCiAgDQogICMgRml4YXRpb24gZHUgc2VlZCBkdSBnw6luw6lyYXRldXIgZGUgbm9tYnJlcyBhbMOpYXRvaXJlcyBwb3VyIHF1J29uIHB1aXNzZSByZXByb2R1aXJlIGxlcyByw6lzdWx0YXRzIHN1ciBkJ2F1dHJlcyBtYWNoaW5lcyBkZSBtYW5pw6hyZSBleGFjdGUNCiAgc2V0LnNlZWQoMTExMTEpDQogIA0KICAjIEVudHJhaW5lbWVudCBkdSBtb2TDqGxlDQogIHJldHVybih4Z2IudHJhaW4oZGF0YSA9IHRyYWluLA0KICAgICAgICAgICAgICAgICAgIG51bV9jbGFzcyA9IDYsICMgQ2xhc3NpZmljYXRpb24gw6AgNiBjbGFzc2VzDQogICAgICAgICAgICAgICAgICAgbnRocmVhZCA9IDEsICMgMSBjb2V1ciB1dGlsaXPDqQ0KICAgICAgICAgICAgICAgICAgIG5yb3VuZHMgPSBucm91bmRzLCAjIE5vbWJyZSBkJ2l0w6lyYXRpb25zIGRlIGJvb3N0aW5nDQogICAgICAgICAgICAgICAgICAgbnVtX3BhcmFsbGVsX3RyZWVzID0gbnVtX3BhcmFsbGVsX3RyZWVzLCAjIE5vbWJyZSBkJ2FyYnJlcyBwb3VyIGxlIG1vZGUgUmFuZG9tIEZvcmVzdA0KICAgICAgICAgICAgICAgICAgIHN1YnNhbXBsZSA9IGlmZWxzZShudW1fcGFyYWxsZWxfdHJlZXMgPiAxLCAwLjYzMiwgMSksICMgQm9vdHN0cmFwIGRlcyBkb25uw6llcyBwb3VyIGwnw6ljaGFudGlsbG9ubmFnZSBlbiBtb2RlIFJhbmRvbSBGb3Jlc3QNCiAgICAgICAgICAgICAgICAgICBldGEgPSAwLjEwLCAjIFNocmlua2FnZSBwb3VyIGxlIGJvb3N0aW5nDQogICAgICAgICAgICAgICAgICAgYm9vc3RlciA9IGJvb3N0ZXIsICMgVHlwZSBkJ2VudHJhaW5lbWVudCA6IGxpbsOpYWlyZSBvdSBub24tbGluw6lhaXJlDQogICAgICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gIm11bHRpOnNvZnRwcm9iIiwgIyBHcmFkaWVudC9IZXNzaWFuIHBvdXIgbCdvcHRpbWlzYXRpb24gcGFyIEdyYWRpZW50IERlc2NlbnQNCiAgICAgICAgICAgICAgICAgICBldmFsX21ldHJpYyA9ICJtZXJyb3IiLCAjIEluZXhhY3RpdHVkZSBkZSBsYSBjbGFzc2lmaWNhdGlvbg0KICAgICAgICAgICAgICAgICAgIG1heGltaXplID0gRkFMU0UsICMgTWluaW1pc2F0aW9uIGRlIGwnZXJyZXVyDQogICAgICAgICAgICAgICAgICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gMTAwLCAjIEFycsOqdCBhcHLDqHMgMTAwIGl0w6lyYXRpb25zIHNhbnMgYW3DqWxpb3JhdGlvbiBkZSBsYSBtw6l0cmlxdWUNCiAgICAgICAgICAgICAgICAgICB2ZXJib3NlID0gRkFMU0UsICMgU2FucyBwcmludCBkZXMgaXTDqXJhdGlvbnMNCiAgICAgICAgICAgICAgICAgICB3YXRjaGxpc3QgPSBsaXN0KHRlc3QgPSB0ZXN0KSwgIyBFc3RpbWF0aW9uIHN1ciBsZXMgZG9ubsOpZXMgZGUgdGVzdA0KICAgICAgICAgICAgICAgICAgIGNhbGxiYWNrcyA9IGxpc3QoY2IuZXZhbHVhdGlvbi5sb2coKSkpKSAjIExvZ2dpbmcgZGVzIGRvbm7DqWVzIGQnZW50cmFpbmVtZW50IHBvdXIgcG91dm9pciByw6ljdXDDqXJlciBsZXMgbcOpdHJpcXVlcw0KfQ0KDQpoMm9fbm5fdHJhaW4gPC0gZnVuY3Rpb24odHJhaW4sIHRlc3QsIG1vZGVsX2lkLCBhY3RpdmF0aW9uLCBoaWRkZW4pIHsNCiAgDQogIHJldHVybih0ZW1wX21vZGVsIDwtIGgyby5kZWVwbGVhcm5pbmcoeSA9IDEsICMgTGFiZWwgPSAxw6hyZSBjb2xvbm5lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWxpZGF0aW9uX2ZyYW1lID0gdGVzdCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9pZCA9IG1vZGVsX2lkLCAjIE5vbSBkdSBtb2TDqGxlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhbmRhcmRpemUgPSBGQUxTRSwgIyBQYXMgZGUgc3RhbmRhcmRpc2F0aW9uIGRlcyBkb25uw6llcywgcHVpc3F1ZSBbLTEsIDFdDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWN0aXZhdGlvbiA9IGFjdGl2YXRpb24sICMgQWN0aXZhdGlvbiBmaW5hbGUgZHUgcsOpc2VhdSBkZSBuZXVyb25lcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhpZGRlbiA9IGhpZGRlbiwgIyBBcmNoaXRlY3R1cmUgZHUgcsOpc2VhdSBkZSBuZXVyb25lcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVwb2NocyA9IDEwMCwgIyBOb21icmUgZGUgcGFzc2VzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbG9zcyA9ICJDcm9zc0VudHJvcHkiLCAjIE9wdGltaXNhdGlvbiBTb2Z0bWF4DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGlzdHJpYnV0aW9uID0gIm11bHRpbm9taWFsIiwgIyBDbGFzc2lmaWNhdGlvbiBtdWx0aS1jbGFzcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0b3BwaW5nX3JvdW5kcyA9IDEwLCAjIEFycsOqdCBhcHLDqHMgMTAgaXTDqXJhdGlvbnMgc2FucyBhbcOpbGlvcmF0aW9uIHNww6ljaWZpcXVlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RvcHBpbmdfbWV0cmljID0gIm1pc2NsYXNzaWZpY2F0aW9uIiwgIyBNaW5pbWlzYXRpb24gZGUgbCdlcnJldXINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdG9wcGluZ190b2xlcmFuY2UgPSAwLjAwMDAxLCAjIFRvbMOpcmFuY2UgbWF4aW1hbGUgZGUgMC4wMDElIGRlIHN0YWduYXRpb24gZGUgbCdlcnJldXINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXByb2R1Y2libGUgPSBUUlVFLCAjIFRlbnRhdGl2ZSBkZSByw6lzdWx0YXRzIHJlcHJvZHVjdGlibGVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDApKSAjIFJlcHJvZHVjdGlvbiBkZXMgcsOpc3VsdGF0cw0KICANCn0NCg0KIyBCb3VjbGUgZCfDqXZhbHVhdGlvbg0KZm9yIChpIGluIDE6MTIpIHsNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IG1vZMOobGUgZGUgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSAoeGdib29zdCkNCiAgdGVtcF9tb2RlbCA8LSB4Z2JfZHluYW1pY190cmFpbih0cmFpbiA9IHRyYWluaW5nX3hnYltbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSB0ZXN0aW5nX3hnYltbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvb3N0ZXIgPSAiZ2JsaW5lYXIiLCAjIExpbsOpYWlyZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3VuZHMgPSAxMDAwMDAwLCAjIEFycsOqdMOpIGF1IG1laWxsZXVyIHLDqXN1bHRhdA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG51bV9wYXJhbGxlbF90cmVlcyA9IDEpDQogIHhnYi5kdW1wKG1vZGVsID0gdGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgIGZuYW1lID0gcGFzdGUwKGZpbGVfdGFnLCAieGdiX2dsbV8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuanNvbiIpLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgd2l0aF9zdGF0cyA9IFRSVUUsICMgRW5yZWdpc3RyZW1lbnQgZGVzIHN0YXRpc3RpcXVlcyBzaSBtb2TDqGxlIGdidHJlZQ0KICAgICAgICAgICBkdW1wX2Zvcm1hdCA9ICJqc29uIikgIyBEdW1wIGF1IGZvcm1hdCBqc29uLCByw6ktdXRpbGlzYWJsZQ0KICBhY2N1cmFjeVtpLCAyXSA8LSAxIC0gdGVtcF9tb2RlbCRldmFsdWF0aW9uX2xvZ1tbMl1dW3RlbXBfbW9kZWwkYmVzdF9pdGVyYXRpb25dICMgUsOpY3Vww6lyYXRpb24gZHUgbWVpbGxldXIgcsOpc3VsdGF0DQogIA0KICAjIEVudHJhaW5lbWVudCBkdSBtb2TDqGxlIGQnYXJicmUgZGUgZMOpY2lzaW9uICh4Z2Jvb3N0KQ0KICB0ZW1wX21vZGVsIDwtIHhnYl9keW5hbWljX3RyYWluKHRyYWluID0gdHJhaW5pbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IHRlc3RpbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9vc3RlciA9ICJnYnRyZWUiLCAjIE5vbi1saW7DqWFpcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBucm91bmRzID0gMSwgIyBVbiBzZXVsIGFyYnJlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtX3BhcmFsbGVsX3RyZWVzID0gMSkNCiAgeGdiLmR1bXAobW9kZWwgPSB0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgZm5hbWUgPSBwYXN0ZTAoZmlsZV90YWcsICJ4Z2JfZHRfIiwgc3ByaW50ZigiJTAyZCIsIGkpLCAiLmpzb24iKSwgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgIHdpdGhfc3RhdHMgPSBUUlVFLCAjIEVucmVnaXN0cmVtZW50IGRlcyBzdGF0aXN0aXF1ZXMgc2kgbW9kw6hsZSBnYnRyZWUNCiAgICAgICAgICAgZHVtcF9mb3JtYXQgPSAianNvbiIpICMgRHVtcCBhdSBmb3JtYXQganNvbiwgcsOpLXV0aWxpc2FibGUNCiAgYWNjdXJhY3lbaSwgM10gPC0gMSAtIHRlbXBfbW9kZWwkZXZhbHVhdGlvbl9sb2dbWzJdXVsxXSAjIFLDqWN1cMOpcmF0aW9uIGR1IG1laWxsZXVyIHLDqXN1bHRhdA0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbW9kw6hsZSBkZSBSYW5kb20gRm9yZXN0ICh4Z2Jvb3N0KQ0KICB0ZW1wX21vZGVsIDwtIHhnYl9keW5hbWljX3RyYWluKHRyYWluID0gdHJhaW5pbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IHRlc3RpbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9vc3RlciA9ICJnYnRyZWUiLCAjIE5vbi1saW7DqWFpcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBucm91bmRzID0gMSwgIyBVbmUgc2V1bGUgaXTDqXJhdGlvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG51bV9wYXJhbGxlbF90cmVlcyA9IDIwMCkgIyBEZSAyMDAgYXJicmVzDQogIHhnYi5kdW1wKG1vZGVsID0gdGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgIGZuYW1lID0gcGFzdGUwKGZpbGVfdGFnLCAieGdiX3JmXyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5qc29uIiksICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICB3aXRoX3N0YXRzID0gVFJVRSwgIyBFbnJlZ2lzdHJlbWVudCBkZXMgc3RhdGlzdGlxdWVzIHNpIG1vZMOobGUgZ2J0cmVlDQogICAgICAgICAgIGR1bXBfZm9ybWF0ID0gImpzb24iKSAjIER1bXAgYXUgZm9ybWF0IGpzb24sIHLDqS11dGlsaXNhYmxlDQogIGFjY3VyYWN5W2ksIDRdIDwtIDEgLSB0ZW1wX21vZGVsJGV2YWx1YXRpb25fbG9nW1syXV0gIyBSw6ljdXDDqXJhdGlvbiBkdSBtZWlsbGV1ciByw6lzdWx0YXQNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IG1vZMOobGUgZCdhcmJyZSBkZSBkw6ljaXNpb24gYm9vc3TDqSBhdmVjIHByb3RlY3Rpb24gY29udHJlIGwnb3ZlcmZpdHRpbmcgKHhnYm9vc3QpDQogIHRlbXBfbW9kZWwgPC0geGdiX2R5bmFtaWNfdHJhaW4odHJhaW4gPSB0cmFpbmluZ194Z2JbW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdGluZ194Z2JbW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib29zdGVyID0gImdidHJlZSIsICMgTm9uLWxpbsOpYWlyZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3VuZHMgPSAxMDAwMDAwLCAjIEFycsOqdMOpIGF1IG1laWxsZXVyIHLDqXN1bHRhdA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG51bV9wYXJhbGxlbF90cmVlcyA9IDEpDQogIHhnYi5kdW1wKG1vZGVsID0gdGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgIGZuYW1lID0gcGFzdGUwKGZpbGVfdGFnLCAieGdiX2didF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuanNvbiIpLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgd2l0aF9zdGF0cyA9IFRSVUUsICMgRW5yZWdpc3RyZW1lbnQgZGVzIHN0YXRpc3RpcXVlcyBzaSBtb2TDqGxlIGdidHJlZQ0KICAgICAgICAgICBkdW1wX2Zvcm1hdCA9ICJqc29uIikgIyBEdW1wIGF1IGZvcm1hdCBqc29uLCByw6ktdXRpbGlzYWJsZQ0KICBhY2N1cmFjeVtpLCA1XSA8LSAxIC0gdGVtcF9tb2RlbCRldmFsdWF0aW9uX2xvZ1tbMl1dW3RlbXBfbW9kZWwkYmVzdF9pdGVyYXRpb25dICMgUsOpY3Vww6lyYXRpb24gZHUgbWVpbGxldXIgcsOpc3VsdGF0DQogIA0KICAjIEVudHJhaW5lbWVudCBkdSBtb2TDqGxlIGRlIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUgKGgybykNCiAgdGVtcF9tb2RlbCA8LSBoMm8uZ2xtKHkgPSAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbmluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHZhbGlkYXRpb25fZnJhbWUgPSB0ZXN0aW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgPSBwYXN0ZTAoImgyb19nbG1fIiwgc3ByaW50ZigiJTAyZCIsIGkpKSwgIyBOb20gZHUgbW9kw6hsZQ0KICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2l0ZXJhdGlvbnMgPSAxMDAsICMgMTAwIGl0w6lyYXRpb25zIGQnb3B0aW1pc2F0aW9uDQogICAgICAgICAgICAgICAgICAgICAgICBzb2x2ZXIgPSAiSVJMU00iLCAjIFNvbHZldXIgcGFyIGTDqWZhdXQNCiAgICAgICAgICAgICAgICAgICAgICAgIHN0YW5kYXJkaXplID0gRkFMU0UsICMgUGFzIGRlIHN0YW5kYXJkaXNhdGlvbiBwdWlzcXVlIFstMSwgMV0NCiAgICAgICAgICAgICAgICAgICAgICAgIGZhbWlseSA9ICJtdWx0aW5vbWlhbCIsICMgQ2xhc3NpZmljYXRpb24gbXVsdGktY2xhc3NlDQogICAgICAgICAgICAgICAgICAgICAgICBzZWVkID0gMCwgIyBSZXByb2R1Y3Rpb24gZGVzIHLDqXN1bHRhdHMNCiAgICAgICAgICAgICAgICAgICAgICAgIGludGVyY2VwdCA9IFRSVUUpDQogIGgyby5kb3dubG9hZF9wb2pvKHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICAgICAgICAgICBwYXRoID0gZmlsZV9oMm8sICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICAgICAgICAgICBnZXRfamFyID0gRkFMU0UpICMgUGFzIGRlIGZpY2hpZXIgLmphcg0KICBhY2N1cmFjeVtpLCA2XSA8LSB0ZW1wX21vZGVsQG1vZGVsJHZhbGlkYXRpb25fbWV0cmljc0BtZXRyaWNzJGhpdF9yYXRpb190YWJsZVsxLCAyXQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbW9kw6hsZSBkJ2FyYnJlIGRlIGTDqWNpc2lvbiAoaDJvKQ0KICB0ZW1wX21vZGVsIDwtIGgyby5yYW5kb21Gb3Jlc3QoeSA9IDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbmluZ19mcmFtZSA9IHRyYWluaW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsaWRhdGlvbl9mcmFtZSA9IHRlc3RpbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9pZCA9IHBhc3RlMCgiaDJvX2R0XyIsIHNwcmludGYoIiUwMmQiLCBpKSksICMgTm9tIGR1IG1vZMOobGUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZV9yYXRlID0gMSwgIyBUb3V0ZXMgbGVzIG9ic2VydmF0aW9ucyBzZXJvbnQgcHJpc2VzIGVuIGNvbXB0ZSBwb3VyIGxlIHNldWwgYXJicmUgZGUgZMOpY2lzaW9uDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJpZXMgPSAzNiwgIyBUb3V0ZXMgbGVzIGZlYXR1cmVzIHNlcm9udCBwcmlzZXMgZW4gY29tcHRlIHBvdXIgbGUgc2V1bCBhcmJyZSBkZSBkw6ljaXNpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlcyA9IDEsICMgVW4gc2V1bCBhcmJyZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDApICMgUmVwcm9kdWN0aW9uIGRlcyByw6lzdWx0YXRzDQogIGgyby5kb3dubG9hZF9wb2pvKHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICAgICAgICAgICBwYXRoID0gZmlsZV9oMm8sICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICAgICAgICAgICBnZXRfamFyID0gRkFMU0UpICMgUGFzIGRlIGZpY2hpZXIgLmphcg0KICBhY2N1cmFjeVtpLCA3XSA8LSAxIC0gbWluKHRlbXBfbW9kZWxAbW9kZWwkc2NvcmluZ19oaXN0b3J5JHZhbGlkYXRpb25fY2xhc3NpZmljYXRpb25fZXJyb3IsIG5hLnJtID0gVFJVRSkNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IG1vZMOobGUgZGUgUmFuZG9tIEZvcmVzdCAoaDJvKQ0KICB0ZW1wX21vZGVsIDwtIGgyby5yYW5kb21Gb3Jlc3QoeSA9IDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbmluZ19mcmFtZSA9IHRyYWluaW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsaWRhdGlvbl9mcmFtZSA9IHRlc3RpbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9pZCA9IHBhc3RlMCgiaDJvX3JmXyIsIHNwcmludGYoIiUwMmQiLCBpKSksICMgTm9tIGR1IG1vZMOobGUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZV9yYXRlID0gMC42MzIsICMgQm9vdHN0cmFwcGluZyAuNjMyIHBvdXIgY2hhcXVlIGFyYnJlIGRlIGTDqWNpc2lvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXRyaWVzID0gLTEsICMgc3FydCgzNikgZmVhdHVyZXMgc2Vyb250IHByaXNlcyBlbiBjb21wdGUgcG91ciBjaGFxdWUgYXJicmUgZGUgZMOpY2lzaW9uDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZXMgPSAyMDAsICMgMjAwIGFyYnJlcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDApICMgUmVwcm9kdWN0aW9uIGRlcyByw6lzdWx0YXRzDQogIGgyby5kb3dubG9hZF9wb2pvKHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICAgICAgICAgICBwYXRoID0gZmlsZV9oMm8sICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICAgICAgICAgICBnZXRfamFyID0gRkFMU0UpICMgUGFzIGRlIGZpY2hpZXIgLmphcg0KICBhY2N1cmFjeVtpLCA4XSA8LSAxIC0gbWluKHRlbXBfbW9kZWxAbW9kZWwkc2NvcmluZ19oaXN0b3J5JHZhbGlkYXRpb25fY2xhc3NpZmljYXRpb25fZXJyb3IsIG5hLnJtID0gVFJVRSkNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IG1vZMOobGUgZCdhcmJyZSBkZSBkw6ljaXNpb24gYm9vc3TDqSBhdmVjIHByb3RlY3Rpb24gY29udHJlIGwnb3ZlcmZpdHRpbmcgKGgybykNCiAgdGVtcF9tb2RlbCA8LSBoMm8uZ2JtKHkgPSAxLA0KICAgICAgICAgICAgICAgICAgICAgIHRyYWluaW5nX2ZyYW1lID0gdHJhaW5pbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgdmFsaWRhdGlvbl9mcmFtZSA9IHRlc3RpbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgPSBwYXN0ZTAoImgyb19nYnRfIiwgc3ByaW50ZigiJTAyZCIsIGkpKSwgIyBOb20gZHUgbW9kw6hsZQ0KICAgICAgICAgICAgICAgICAgICAgIGRpc3RyaWJ1dGlvbiA9ICJtdWx0aW5vbWlhbCIsICMgQ2xhc3NpZmljYXRpb24gbXVsdGktY2xhc3NlDQogICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3JhdGUgPSAxLCAjIFBhcyBkZSBwcm9jZXNzdXMgc3RvY2hhc3RpcXVlDQogICAgICAgICAgICAgICAgICAgICAgbnRyZWVzID0gMTAwLCAjIDEwMCBpdMOpcmF0aW9ucyBkZSBib29zdGluZyBhdSBtYXhpbXVtDQogICAgICAgICAgICAgICAgICAgICAgc2NvcmVfZWFjaF9pdGVyYXRpb24gPSBUUlVFLCAjIE5vdGVyIGxhIHZhbGV1ciBkZSBjaGFxdWUgaXTDqXJhdGlvbg0KICAgICAgICAgICAgICAgICAgICAgIHN0b3BwaW5nX3JvdW5kcyA9IDEwLCAjIEFycsOqdCBhcHLDqHMgMTAgaXTDqXJhdGlvbnMgc2FucyBhbcOpbGlvcmF0b24gZGUgbGEgbcOpdHJpcXVlDQogICAgICAgICAgICAgICAgICAgICAgc3RvcHBpbmdfbWV0cmljID0gIm1pc2NsYXNzaWZpY2F0aW9uIiwgIyBTdXJ2ZWlsbGVyIGwnaW5leGFjdGl0dWRlIGRlIGxhIGNsYXNzaWZpY2F0aW9uIHBvdXIgbCdhcnLDqnQNCiAgICAgICAgICAgICAgICAgICAgICBzdG9wcGluZ190b2xlcmFuY2UgPSAwLjAwMDAxLCAjIEFycsOqdGVyIGxvcnNxdWUgbGEgbcOpdHJpcXVlIHN0YWduZSBkZSAwLjAwMSUNCiAgICAgICAgICAgICAgICAgICAgICBzZWVkID0gMCkgIyBSZXByb2R1Y3Rpb24gZGVzIHLDqXN1bHRhdHMNCiAgaDJvLmRvd25sb2FkX3Bvam8odGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgICAgICAgICAgIHBhdGggPSBmaWxlX2gybywgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgICAgICAgICAgIGdldF9qYXIgPSBGQUxTRSkgIyBQYXMgZGUgZmljaGllciAuamFyDQogIGFjY3VyYWN5W2ksIDldIDwtIDEgLSBtaW4odGVtcF9tb2RlbEBtb2RlbCRzY29yaW5nX2hpc3RvcnkkdmFsaWRhdGlvbl9jbGFzc2lmaWNhdGlvbl9lcnJvciwgbmEucm0gPSBUUlVFKQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgcsOpc2VhdSBkZSBuZXVyb25lcyDDoCBhcmNoaXRlY3R1cmUgMzJ4NiArIFJlTFUgKGgybykNCiAgdGVtcF9tb2RlbCA8LSBoMm9fbm5fdHJhaW4odHJhaW4gPSB0cmFpbmluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IHRlc3RpbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2lkID0gcGFzdGUwKCJoMm9fbm5fMzJ4Nl9SZUxVXyIsIHNwcmludGYoIiUwMmQiLCBpKSksICMgTm9tIGR1IG1vZMOobGUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWN0aXZhdGlvbiA9ICJSZWN0aWZpZXIiLCAjIFJlTFUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGlkZGVuID0gMzIpICMgQXJjaGl0ZWN0dXJlIDMyeDYNCiAgaDJvLmRvd25sb2FkX3Bvam8odGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgICAgICAgICAgIHBhdGggPSBmaWxlX2gybywgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgICAgICAgICAgIGdldF9qYXIgPSBGQUxTRSkgIyBQYXMgZGUgZmljaGllciAuamFyDQogIGFjY3VyYWN5W2ksIDEwXSA8LSAxIC0gbWluKHRlbXBfbW9kZWxAbW9kZWwkc2NvcmluZ19oaXN0b3J5JHZhbGlkYXRpb25fY2xhc3NpZmljYXRpb25fZXJyb3IsIG5hLnJtID0gVFJVRSkNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IHLDqXNlYXUgZGUgbmV1cm9uZXMgw6AgYXJjaGl0ZWN0dXJlIDMyeDYgKyBUYW5oIChoMm8pDQogIHRlbXBfbW9kZWwgPC0gaDJvX25uX3RyYWluKHRyYWluID0gdHJhaW5pbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSB0ZXN0aW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9pZCA9IHBhc3RlMCgiaDJvX25uXzMyeDZfVGFuaF8iLCBzcHJpbnRmKCIlMDJkIiwgaSkpLCAjIE5vbSBkdSBtb2TDqGxlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFjdGl2YXRpb24gPSAiVGFuaCIsICMgIlNpZ21vaWRlIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoaWRkZW4gPSAzMikgIyBBcmNoaXRlY3R1cmUgMzJ4Ng0KICBoMm8uZG93bmxvYWRfcG9qbyh0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgICAgICAgICAgcGF0aCA9IGZpbGVfaDJvLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgICAgICAgICAgZ2V0X2phciA9IEZBTFNFKSAjIFBhcyBkZSBmaWNoaWVyIC5qYXINCiAgYWNjdXJhY3lbaSwgMTFdIDwtIDEgLSBtaW4odGVtcF9tb2RlbEBtb2RlbCRzY29yaW5nX2hpc3RvcnkkdmFsaWRhdGlvbl9jbGFzc2lmaWNhdGlvbl9lcnJvciwgbmEucm0gPSBUUlVFKQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgcsOpc2VhdSBkZSBuZXVyb25lcyDDoCBhcmNoaXRlY3R1cmUgMTZ4MTZ4NiArIFJlTFUgKGgybykNCiAgdGVtcF9tb2RlbCA8LSBoMm9fbm5fdHJhaW4odHJhaW4gPSB0cmFpbmluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IHRlc3RpbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2lkID0gcGFzdGUwKCJoMm9fbm5fMTZ4MTZ4Nl9SZUxVXyIsIHNwcmludGYoIiUwMmQiLCBpKSksICMgTm9tIGR1IG1vZMOobGUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWN0aXZhdGlvbiA9ICJSZWN0aWZpZXIiLCAjIFJlTFUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGlkZGVuID0gYygxNiwgMTYpKSAjIEFyY2hpdGVjdHVyZSAxNngxNng2DQogIGgyby5kb3dubG9hZF9wb2pvKHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICAgICAgICAgICBwYXRoID0gZmlsZV9oMm8sICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICAgICAgICAgICBnZXRfamFyID0gRkFMU0UpICMgUGFzIGRlIGZpY2hpZXIgLmphcg0KICBhY2N1cmFjeVtpLCAxMl0gPC0gMSAtIG1pbih0ZW1wX21vZGVsQG1vZGVsJHNjb3JpbmdfaGlzdG9yeSR2YWxpZGF0aW9uX2NsYXNzaWZpY2F0aW9uX2Vycm9yLCBuYS5ybSA9IFRSVUUpDQogIA0KICAjIEVudHJhaW5lbWVudCBkdSByw6lzZWF1IGRlIG5ldXJvbmVzIMOgIGFyY2hpdGVjdHVyZSAxNngxNng2ICsgVGFuaCAoaDJvKQ0KICB0ZW1wX21vZGVsIDwtIGgyb19ubl90cmFpbih0cmFpbiA9IHRyYWluaW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgPSBwYXN0ZTAoImgyb19ubl8xNngxNng2X1RhbmhfIiwgc3ByaW50ZigiJTAyZCIsIGkpKSwgIyBOb20gZHUgbW9kw6hsZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhY3RpdmF0aW9uID0gIlRhbmgiLCAjICJTaWdtb2lkZSINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGlkZGVuID0gYygxNiwgMTYpKSAjIEFyY2hpdGVjdHVyZSAxNngxNng2DQogIGgyby5kb3dubG9hZF9wb2pvKHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICAgICAgICAgICBwYXRoID0gZmlsZV9oMm8sICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICAgICAgICAgICBnZXRfamFyID0gRkFMU0UpICMgUGFzIGRlIGZpY2hpZXIgLmphcg0KICBhY2N1cmFjeVtpLCAxM10gPC0gMSAtIG1pbih0ZW1wX21vZGVsQG1vZGVsJHNjb3JpbmdfaGlzdG9yeSR2YWxpZGF0aW9uX2NsYXNzaWZpY2F0aW9uX2Vycm9yLCBuYS5ybSA9IFRSVUUpDQogIA0KfQ0KDQojIFRlbXBzIG7DqWNlc3NhaXJlDQp0aW1pbmcoQ3VycmVudFRpbWUsICJDcsOpYXRpb24gZXQgw6l2YWx1YXRpb24gZGVzIGRvdXplIG1vZMOobGVzIGRlIGJlbmNobWFyayIpDQpgYGANCg0KIyMgQW5hbHlzZSBkZXMgcsOpc3VsdGF0cyBwb3VyIGxlcyBkb3V6ZSBtb2TDqGxlcw0KDQpOb3VzIHJlbWFycXVvbnMgY2xhaXJlbWVudCBsZSBwcm9ibMOobWUgcXUnb24gYSBzb3VsZXbDqSBkw6lzIGxlIGTDqWJ1dCA6IGxhIHNhbGxlIDEgcG9zc8OoZGUgZGVzIGZlYXR1cmVzIHF1aSBuZSBzb250IHBhcyBhZMOpcXVhdGVzIGVuIGwnw6l0YXQsIGxlcyBkb25uw6llcyBzZW1ibGUgYmllbiBpbnV0aWxpc2FibGVzIHNhbnMgdHJhbnNmb3JtYXRpb24gcHLDqWFsYWJsZS4NCg0KRGUgZmFpdCwgbGVzIHLDqXN1bHRhdHMgbmUgc29udCBwYXMgZW5jb3JlIGV4cGxvaXRhYmxlcyBkZSBtYW5pw6hyZSBjb3JyZWN0ZS4NCg0KYGBge3IgUmVzdWx0YXRzMX0NCiMgTW95ZW5uZSBkZXMgcsOpc3VsdGF0cw0KZm9yIChpIGluIDI6MTMpIHsNCiAgYWNjdXJhY3lbMTMsIGldIDwtIG1lYW4oYWNjdXJhY3lbMTo2LCBpXSkNCiAgYWNjdXJhY3lbMTQsIGldIDwtIG1lYW4oYWNjdXJhY3lbNzo5LCBpXSkNCiAgYWNjdXJhY3lbMTUsIGldIDwtIG1lYW4oYWNjdXJhY3lbMTA6MTIsIGldKQ0KICBhY2N1cmFjeVsxNiwgaV0gPC0gbWVhbihhY2N1cmFjeVsxMzoxNSwgaV0pDQp9DQoNCiMgRW5yZWdpc3RyZW1lbnQgZGVzIHNjb3Jlcw0KZndyaXRlKGFjY3VyYWN5LCAic2NvcmVzLzFfbW9kZWxzLmNzdiIpDQoNCiMgQWZmaWNoYWdlIGRlcyByw6lzdWx0YXRzIGRhbnMgdW4gdGFibGVhdSBpbnRlcmFjdGlmDQp0b19wcmludCA8LSBkYXRhLnRhYmxlKHQoYWNjdXJhY3lbMTM6MTYsIC0xXSkpICMgUHLDqXBhcmF0aW9uIGRlcyBkb25uw6llcyDDoCBtZXR0cmUgc3VyIHRhYmxlDQpjb2xuYW1lcyh0b19wcmludCkgPC0gYygiMSBjb250cmUgMSIsICIxIGNvbnRyZSAyIiwgIjIgY29udHJlIDEiLCAiTW95ZW5uZSIpICMgUmVtaXNlIGRlcyBub21zIGRlcyBjb2xvbm5lcw0Kcm93Lm5hbWVzKHRvX3ByaW50KSA8LSBjb2xuYW1lcyhhY2N1cmFjeSlbLTFdICMgUmVtaXNlIGRlcyBub21zIGRlcyBsaWduZXMNCmRhdGF0YWJsZSh0b19wcmludCwNCiAgICAgICAgICBmaWx0ZXIgPSAidG9wIiwgIyBGaWx0cmFnZSBhdS1kZXNzdXMgZGUgbGEgdGFibGUNCiAgICAgICAgICBjbGFzcyA9ICJjZWxsLWJvcmRlciBzdHJpcGUiLCAjIENTUw0KICAgICAgICAgIGV4dGVuc2lvbnMgPSBjKCJDb2xSZW9yZGVyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAiUm93UmVvcmRlciIpLCAjIFJlb3Jkb25uZXIgbWFudWVsbGVtZW50IMOgIGxhIG1haW4NCiAgICAgICAgICBvcHRpb25zID0gbGlzdChwYWdlTGVuZ3RoID0gMTIsICMgUGFnZSBhZmZpY2hhbnQgMTIgbGlnbmVzDQogICAgICAgICAgICAgICAgICAgICAgICAgb3JkZXIgPSBsaXN0KGxpc3QoNCwgImRlc2MiKSksICMgT3Jkb25uZXIgcGFyIGTDqWZhdXQgcGFyIGwnZXhhY3RpdHVkZSBtb3llbm5lDQogICAgICAgICAgICAgICAgICAgICAgICAgY29sUmVvcmRlciA9IFRSVUUsICMgUGx1Z2luDQogICAgICAgICAgICAgICAgICAgICAgICAgcm93UmVvcmRlciA9IFRSVUUpKSAlPiUgIyBQbHVnaW4NCiAgZm9ybWF0U3R5bGUoYygiMSBjb250cmUgMSIsICIxIGNvbnRyZSAyIiwgIjIgY29udHJlIDEiKSwNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmQgPSBzdHlsZUNvbG9yQmFyKGMoMCwgMSksICdsaWdodGdyZWVuJyksICMgQ291bGV1ciB2ZXJ0IGNsYWlyIHBvdXIgbGVzIG3DqXRyaXF1ZXMgcGFyIGZvbGQNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRTaXplID0gJzEwMCUgOTAlJywNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRSZXBlYXQgPSAnbm8tcmVwZWF0JywNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRQb3NpdGlvbiA9ICdjZW50ZXInKSAlPiUNCiAgZm9ybWF0U3R5bGUoIk1veWVubmUiLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihjKDAsIDEpLCAncGluaycpLCAjIENvdWxldXIgcm9zZSBwb3VyIGxhIG3DqXRyaXF1ZSBkZSBtb3llbm5lDQogICAgICAgICAgICAgIGJhY2tncm91bmRTaXplID0gJzEwMCUgOTAlJywNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFJlcGVhdCA9ICduby1yZXBlYXQnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUG9zaXRpb24gPSAnY2VudGVyJykgJT4lDQogIGZvcm1hdFBlcmNlbnRhZ2UoY29sdW1ucyA9IGMoIjEgY29udHJlIDEiLCAiMSBjb250cmUgMiIsICIyIGNvbnRyZSAxIiksDQogICAgICAgICAgICAgIGRpZ2l0cyA9IDgpICU+JQ0KICBmb3JtYXRQZXJjZW50YWdlKGNvbHVtbnMgPSAiTW95ZW5uZSIsDQogICAgICAgICAgICAgIGRpZ2l0cyA9IDgpDQoNCiMgQWZmaWNoYWdlIGRlcyByw6lzdWx0YXRzIGRhbnMgdW4gdGFibGVhdSBzdGF0aXF1ZQ0KZm9ybWF0dGFibGUoYWNjdXJhY3lbLCBjKDEsIDI6NSldLCBsaXN0KGZvcm1hdHRhYmxlOjphcmVhKGNvbCA9IHhnYl9MaW5lYXJNb2RlbDp4Z2JfR3JhZGllbnRCb29zdGluZykgfiBjb2xvcl9iYXIoIm9yYW5nZSIpKSkNCmZvcm1hdHRhYmxlKGFjY3VyYWN5WywgYygxLCA2OjkpXSwgbGlzdChmb3JtYXR0YWJsZTo6YXJlYShjb2wgPSBoMm9fTGluZWFyTW9kZWw6aDJvX0dyYWRpZW50Qm9vc3RpbmcpIH4gY29sb3JfYmFyKCJjeWFuIikpKQ0KZm9ybWF0dGFibGUoYWNjdXJhY3lbLCBjKDEsIDEwOjEzKV0sIGxpc3QoZm9ybWF0dGFibGU6OmFyZWEoY29sID0gaDJvX05OXzMyeDZfUmVMVTpoMm9fTk5fMTZ4MTZ4Nl9Tb2Z0KSB+IGNvbG9yX2JhcigieWVsbG93IikpKQ0KYGBgDQoNCiMgU2Vjb25kZSBBbmFseXNlIEV4cGxvcmF0b2lyZQ0KDQpJbCBmYXVkcmEgcHLDqXBhcmVyIGNvcnJlY3RlbWVudCBsZXMgZG9ubsOpZXMuIFBvdXIgY2VsYSwgZGV1eCDDqXRhcGVzIDoNCg0KLSBOZXR0b3llciBsZXMgZG9ubsOpZXMNCi0gQ3LDqWVyIGxlcyBmZWF0dXJlcyBkZSBub3V2ZWF1DQoNCkVuc3VpdGUsIG9uIHBvdXJyYSByZWZhaXJlIGwnYW5hbHlzZSBzeXN0w6ltaXF1ZS4NCg0KIyMgTmV0dG95YWdlIGRlcyBkb25uw6llcw0KDQpPbiBwZXV0IHRlbnRlciBkZSB0cmF2YWlsbGVyIHN1ciBsYSByw6lzb2x1dGlvbiBkdSBwcm9ibMOobWUgZGVzIHNpZ25hdXggbWl4w6lzLCBhaW5zaSBxdWUgbGUgcHJvYmzDqG1lIGRlIGdhaW4uDQoNClN1cHBvc29ucyBxdWUgbGEgU2FsbGUgMiBhIGJpZW4gw6l0w6kgcGFpcsOpZSBhdmVjIGxhIFNhbGxlIDEsIGFsb3JzIDoNCg0KLSBMZXMgc2lnbmF1eCBtaXjDqXMgZGV2cmFpZW50IGF2b2lyIGxldXIgc2lnbmUgaW52ZXJzw6kgKG1pcyDDoCBwYXJ0IGxlcyBmbHVjdHVhdGlvbnMgZGUgZ2FpbikNCi0gTGVzIHNpZ25hdXggZmFpYmxlcyBkZXZyYWllbnQgYXZvaXIgbGV1ciBjb2VmZmljaWVudCBtdWx0aXBsaWNhdGV1ciBhdWdtZW50w6kgKG1pcyDDoCBwYXJ0IGxlcyBmbHVjdHVhdGlvbnMgZGUgZ2FpbikNCg0KT24gc2UgcmV0cm91dmUgZG9uYyBhdmVjIHVuIG1vZMOobGUgZGUgdHlwZSBmKHgpID0gYXgrYiwgcXVpIGVzdCB1biBwcm9ibMOobWUgbGluw6lhaXJlIGZhY2lsZW1lbnQgb3B0aW1pc2FibGUuIE5vdXMgYWxsb25zIHV0aWxpc2VyIGxhIG3DqXRob2RlIGRlcyBtb2luZHJlcyBjYXJyw6lzIHBvdXIgYWp1c3RlciBsZXMgdmFsZXVycyBkZSBsYSBTYWxsZSAxLCDDoCBwYXJ0aXIgZGUgY2VsbGVzIGRlIGxhIFNhbGxlIDIuDQoNCkFmaW4gZGUgbmUgcGFzIGludHJvZHVpcmUgZGUgbGVha2FnZSwgbm91cyB0cmF2YWlsbGVyb25zIGF1IG5pdmVhdSBnbG9iYWwgKHNhbnMgcHJlbmRyZSBlbiBjb21wdGUgbGUgbGFiZWwpIGRlcyBzYWxsZXMsIGNlIHF1aSBuJ2VzdCBwYXMgZm9yY8OpbWVudCBsYSBtZWlsbGV1cmUgbcOpdGhvZGUgKG1haXMgZXN0IHBsdXMgc8O7cmUpLiBMZXMgcsOpc3VsdGF0cyBzZW1ibGVudCBiaWVuIG1laWxsZXVycyBkJ2FwcsOocyBsZSBncmFwaGlxdWUuIE5vdXMgcG91cnJvbnMgdGVzdGVyIG5vdHJlIGh5cG90aMOoc2UgZCdhbcOpbGlvcmF0aW9uIGRlcyByw6lzdWx0YXRzIGRhbnMgbGEgcHJvY2hhaW5lIGFuYWx5c2Ugc3lzdMOpbWlxdWUuDQoNCmBgYHtyIE5ldHRveWFnZX0NCiMgQ29tcHRldXIgZGUgdGVtcHMNCkN1cnJlbnRUaW1lIDwtIHRpbWVyKCkgIyBDaHVuayBOZXR0b3lhZ2UgZGVzIGRvbm7DqWVzDQoNCiMgU2F1dmVnYXJkZSBkZXMgYW5jaWVubmVzIGRvbm7DqWVzIHBhciBkZWVwIGNvcHkNCmRhdGFfcHJlX29sZCA8LSBsaXN0KCkNCmZvciAoaSBpbiAxOjMxNCkgew0KICBkYXRhX3ByZV9vbGRbW2ldXSA8LSBjb3B5KGRhdGFfcHJlW1tpXV0pICMgUmVwbGljYXRpb24gZW4gbcOpbW9pcmUgYXUgbGlldSBkZSBsYSBjb3BpZSBkdSBwb2ludGV1cg0KfQ0KDQojIEJvdWNsZSBwYXIgYW5jcmUNCmZvciAoaSBpbiAxOjQpIHsNCiAgDQogICMgUmVncm91cGFnZSBkZXMgZG9ubsOpZXMgc2Vsb24gbCdhbmNyZSBldCBsYSBzYWxsZQ0KICB0ZW1wX3NhbGxlMSA8LSBhdmdfc2VyaWVzWyhBbmNob3IgPT0gcGFzdGUwKCJBbmNyZSIsIGkpKSAmIChSb29tID09ICJTYWxsZTEiKSwgXSRTdHJlbmd0aA0KICB0ZW1wX3NhbGxlMiA8LSBhdmdfc2VyaWVzWyhBbmNob3IgPT0gcGFzdGUwKCJBbmNyZSIsIGkpKSAmIChSb29tID09ICJTYWxsZTIiKSwgXSRTdHJlbmd0aA0KICANCiAgIyBEw6l0ZXJtaW5hdGlvbiBkdSBtb2TDqGxlIGxpbsOpYWlyZSBwYXIgbGEgbcOpdGhvZGUgZGVzIG1vaW5kcmVzIGNhcnLDqXMNCiAgdGVtcF9tb2RlbCA8LSBmYXN0TG1QdXJlKFggPSBjYmluZChhcy5tYXRyaXgodGVtcF9zYWxsZTEpLCByZXAoMSwgbGVuZ3RoKHRlbXBfc2FsbGUxKSkpLCB5ID0gdGVtcF9zYWxsZTIpDQogIA0KICAjIE5ldHRveWFnZSBkZXMgZG9ubsOpZXMgZCdhcHLDqHMgbGUgY29lZmZpY2llbnQgZGlyZWN0ZXVyIGV0IGwnaW50ZXJzZWN0aW9uDQogIGZvciAoaiBpbiB3aGljaChncm91cF9yb29tJGRhdGFzZXRfSUQgPT0gMSkpIHsNCiAgICBkYXRhX3ByZVtbal1dW1tpXV0gPC0gZGF0YV9wcmVbW2pdXVtbaV1dICogdGVtcF9tb2RlbCRjb2VmZmljaWVudHNbMV0gKyB0ZW1wX21vZGVsJGNvZWZmaWNpZW50c1syXQ0KICB9DQogIA0KfQ0KDQojIFJlbGFuY2VtZW50IGRlIGwnYWdyw6lnYXRpb24gZGVzIGRvbm7DqWVzDQphdmdfc2VyaWVzX2NsZWFuIDwtIGRhdGEudGFibGUobWF0cml4KHJlcCgwLCAxNiAqIDQgKiA2ICogNiksIG5yb3cgPSAxNiAqIDQgKiA2LCBuY29sID0gNCkpDQpjb2xuYW1lcyhhdmdfc2VyaWVzX2NsZWFuKSA8LSBjKCJTdHJlbmd0aCIsICJBbmNob3IiLCAiTGFiZWwiLCAiVGltZSIpDQoNCiMgUHLDqS1maWxsaW5nIGRlcyBmYWN0ZXVycyAoQW5jaG9yLCBMYWJlbCwgVGltZSkNCmF2Z19zZXJpZXNfY2xlYW5bWyJBbmNob3IiXV0gPC0gYXMuZmFjdG9yKHJlcChpbnZlcnNlLnJsZShsaXN0KGxlbmd0aHMgPSByZXAoMTYsIDQpLCB2YWx1ZXMgPSAxOjQpKSwgNikpDQpsZXZlbHMoYXZnX3Nlcmllc19jbGVhbltbIkFuY2hvciJdXSkgPC0gcGFzdGUoIkFuY3JlIiwgMTo0LCBzZXAgPSAiIikNCmF2Z19zZXJpZXNfY2xlYW5bWyJMYWJlbCJdXSA8LSBhcy5mYWN0b3IoaW52ZXJzZS5ybGUobGlzdChsZW5ndGhzID0gcmVwKDE2ICogNCwgNiksIHZhbHVlcyA9IDE6NikpKQ0KbGV2ZWxzKGF2Z19zZXJpZXNfY2xlYW5bWyJMYWJlbCJdXSkgPC0gcGFzdGUoIlRyYWplY3RvaXJlIiwgMTo2LCBzZXAgPSAiIikNCmF2Z19zZXJpZXNfY2xlYW5bWyJUaW1lIl1dIDwtIHJlcCgxOjE2LCA2ICogNCkNCg0KIyBUcmFuc2Zvcm1hdGlvbiBlbiBsaXN0ZSBwYXIgc2FsbGUgw6AgZMOpcGl2b3RlciBwYXIgbGEgc3VpdGUNCmF2Z19zZXJpZXNfY2xlYW4gPC0gbGlzdChjYmluZChhdmdfc2VyaWVzX2NsZWFuLCBSb29tID0gcmVwKDEsIDM4NCkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGNiaW5kKGF2Z19zZXJpZXNfY2xlYW4sIFJvb20gPSByZXAoMiwgMzg0KSksDQogICAgICAgICAgICAgICAgICAgICAgICAgY2JpbmQoYXZnX3Nlcmllc19jbGVhbiwgUm9vbSA9IHJlcCgzLCAzODQpKSkNCg0KIyBQcsOpLWNvbXB0ZSBkdSBub21icmUgZCdvY2N1cnJlbmNlIGRlcyBsYWJlbHMNCmxhYmVsX2NvdW50IDwtIGxpc3QodGFidWxhdGUoZ3JvdXBfcGF0aFtbInBhdGhfSUQiXV1bZ3JvdXBfcm9vbVtbImRhdGFzZXRfSUQiXV0gPT0gMV0pLA0KICAgICAgICAgICAgICAgICAgICB0YWJ1bGF0ZShncm91cF9wYXRoW1sicGF0aF9JRCJdXVtncm91cF9yb29tW1siZGF0YXNldF9JRCJdXSA9PSAyXSksDQogICAgICAgICAgICAgICAgICAgIHRhYnVsYXRlKGdyb3VwX3BhdGhbWyJwYXRoX0lEIl1dW2dyb3VwX3Jvb21bWyJkYXRhc2V0X0lEIl1dID09IDNdKSkNCg0KIyBEw6l0ZXJtaW5hdGlvbiBkZXMgMTYgZGVybmnDqHJlcyBvYnNlcnZhdGlvbnMgKDIgc2Vjb25kZXMgw6AgOCBIeiksIG1veWVubmlzw6llcw0KZm9yIChpIGluIDE6MzE0KSB7DQogIHRlbXBfbGFiZWwgPC0gKChncm91cF9wYXRoW1sicGF0aF9JRCJdXVtpXSAtIDEpICogNjQpICsgMSAjIExpZ25lIGRlIGTDqW1hcnJhZ2UgZGFucyBsYSBtYXRyaWNlIGFncsOpZ8OpZQ0KICB0ZW1wX29icyA8LSBucm93KGRhdGFfcHJlW1tpXV0pIC0gMTUgIyBMaWduZSBkZSBkw6ltYXJyYWdlIGRhbnMgbGEgbWF0cmljZSDDoCBzYXV2ZWdhcmRlcg0KICANCiAgYXZnX3Nlcmllc19jbGVhbltbZ3JvdXBfcm9vbVtbImRhdGFzZXRfSUQiXV1baV1dXVt0ZW1wX2xhYmVsOih0ZW1wX2xhYmVsICsgNjMpLCAxXSA8LSBhdmdfc2VyaWVzX2NsZWFuW1tncm91cF9yb29tW1siZGF0YXNldF9JRCJdXVtpXV1dW3RlbXBfbGFiZWw6KHRlbXBfbGFiZWwgKyA2MyksIDFdICsgKHVubGlzdChkYXRhX3ByZVtbaV1dW3RlbXBfb2JzOih0ZW1wX29icyArIDE1KSwgXSkgLyBsYWJlbF9jb3VudFtbZ3JvdXBfcm9vbVtbImRhdGFzZXRfSUQiXV1baV1dXVtncm91cF9wYXRoW1sicGF0aF9JRCJdXVtpXV0pDQp9DQoNCiMgRMOpcGl2b3RhZ2UgZGUgbGEgdmFyaWFibGUgZMOpZmluaXNzYW50IGxhIHNhbGxlDQphdmdfc2VyaWVzX2NsZWFuIDwtIHJiaW5kKGF2Z19zZXJpZXNfY2xlYW5bWzFdXSwgYXZnX3Nlcmllc19jbGVhbltbMl1dLCBhdmdfc2VyaWVzX2NsZWFuW1szXV0pDQphdmdfc2VyaWVzX2NsZWFuW1siUm9vbSJdXSA8LSBhcy5mYWN0b3IoaW52ZXJzZS5ybGUobGlzdChsZW5ndGhzID0gcmVwKDM4NCwgMyksIHZhbHVlcyA9IDE6MykpKQ0KbGV2ZWxzKGF2Z19zZXJpZXNfY2xlYW5bWyJSb29tIl1dKSA8LSBwYXN0ZSgiU2FsbGUiLCAxOjMsIHNlcCA9ICIiKQ0KDQojIEFmZmljaGFnZSBzb3VzIGZvcm1lIGRlIHBsb3QgaW50ZXJhY3RpZiBkZSBtYW5pw6hyZSBhdXRvbWF0aXPDqWUNCmdncGxvdGx5KGdncGxvdChkYXRhID0gYXZnX3Nlcmllc19jbGVhbiwgYWVzX3N0cmluZyh4ID0gIlRpbWUiLCB5ID0gIlN0cmVuZ3RoIiwgZ3JvdXAgPSAiTGFiZWwiLCBjb2xvciA9ICJMYWJlbCIpKSArIGdlb21fbGluZSgpICsgZ2VvbV9wb2ludCgpICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MiIpICsgdGhlbWVfYncoKSArIGZhY2V0X2dyaWQoQW5jaG9yIH4gUm9vbSkgKyBsYWJzKHRpdGxlID0gIkV2b2x1dGlvbiBkZSBsYSBmb3JjZSBkdSBzaWduYWwgZGUgbCdhbmNyZSAoY29ycmlnw6kpIHBhciByYXBwb3J0IGF1IHRlbXBzIiksIHdpZHRoID0gOTYwLCBoZWlnaHQgPSA3MjApDQoNCiMgRW5yZWdpc3RyZW1lbnQgZGUgbGEgdGFibGUgcG91ciB1c2FnZSB1bHTDqXJpZXVyIHNpIG7DqWNlc3NhaXJlDQpmd3JpdGUoYXZnX3Nlcmllc19jbGVhbiwgImFncmVnYXRpb24vYWdyZWdhdGlvbjIuY3N2IikNCg0KIyBUZW1wcyBuw6ljZXNzYWlyZQ0KdGltaW5nKEN1cnJlbnRUaW1lLCAiTmV0dG95YWdlIGRlcyBkb25uw6llcyIpDQpgYGANCg0KIyMgQ3LDqWF0aW9uIGRlcyBub3V2ZWxsZXMgZmVhdHVyZXMNCg0KTm91cyBwb3V2b25zIGTDqXNvcm1haXMgY3LDqWVyIGRlcyBmZWF0dXJlcyBuZXR0b3nDqWVzLiBDZWxhIG5vdXMgc2VydmlyYSBkYW5zIGxhIHByb2NoYWluZSBhbmFseXNlIHN5c3TDqW1pcXVlLg0KDQpgYGB7ciBDcmVhdGlvbkZlYXR1cmVzMn0NCiMgQ29tcHRldXIgZGUgdGVtcHMNCkN1cnJlbnRUaW1lIDwtIHRpbWVyKCkgIyBDaHVuayBDcsOpYXRpb24gZGVzIGZlYXR1cmVzIG5ldHRvecOpZXMNCg0KIyBQcsOpLWluaXRpYWxpc2F0aW9uIGRlIGxhIGZyYW1lDQptaW5pX2xtIDwtIGRhdGEuZnJhbWUobWF0cml4KG5yb3cgPSAzMTQsIG5jb2wgPSAzNikpDQoNCiMgQm91Y2xlIHBhciBzw6lyaWUgdGVtcG9yZWxsZQ0KZm9yIChpIGluIDE6MzE0KSB7DQogIA0KICAjIEJvdWNsZSBwYXIgYW5jcmUNCiAgZm9yIChqIGluIDE6NCkgew0KICAgIA0KICAgICMgRW50cmFpbmVtZW50IGQndW4gbW9kw6hsZSBsaW7DqWFpcmUgdXRpbGlzYW50IGxlcyBhdXRyZXMgYW5jcmVzLCBhdmVjIGwnaW50ZXJjZXB0cmljZQ0KICAgIHRlbXBfbW9kZWwgPC0gZmFzdExtUHVyZShYID0gY2JpbmQoYXMubWF0cml4KGRhdGFfcHJlW1tpXV1bLCAoMTo0KVstal0sIHdpdGggPSBGQUxTRV0pLCByZXAoMSwgbnJvdyhkYXRhX3ByZVtbaV1dKSkpLCB5ID0gZGF0YV9wcmVbW2ldXVtbal1dKQ0KICAgIA0KICAgICMgRW5yZWdpc3RyZW1lbnQgZGVzIGNvZWZmaWNpZW50cyBldCBkZXMgcsOpc2lkdXMNCiAgICBtaW5pX2xtW2ksIChqICogOCAtIDcpOihqICogOCldIDwtIGModGVtcF9tb2RlbCRjb2VmZmljaWVudHMsIHRlbXBfbW9kZWwkc3RkZXJyKQ0KICAgIA0KICB9DQogIA0KICAjIEFqb3V0IGR1IGRlcm5pZXIgw6lsw6ltZW50IGRlIGxhIHPDqXJpZSB0ZW1wb3JlbGxlICg0IGFuY3JlcykNCiAgbWluaV9sbVtpLCAzMzozNl0gPC0gZGF0YV9wcmVbW2ldXVtucm93KGRhdGFfcHJlW1tpXV0pLCBdDQogIA0KfQ0KDQojIEVucmVnaXN0cmVtZW50IGRlcyBkb25uw6llcyBhdSBmb3JtYXQgQ1NWDQpmd3JpdGUoY2JpbmQobWluaV9sbSwgR3JvdXAgPSBncm91cF9yb29tW1siZGF0YXNldF9JRCJdXSwgTGFiZWwgPSBncm91cF9wYXRoW1sicGF0aF9JRCJdXSksICJmZWF0dXJlcy9mZWF0dXJlczIuY3N2IikNCg0KIyBUZW1wcyBuw6ljZXNzYWlyZQ0KdGltaW5nKEN1cnJlbnRUaW1lLCAiQ3LDqWF0aW9uIGRlcyBmZWF0dXJlcyBuZXR0b3nDqWVzIikNCmBgYA0KDQojIFNlY29uZGUgQW5hbHlzZSBTeXN0w6ltaXF1ZQ0KDQpWdSBxdWUgbm91cyBkaXNwb3NvbnMgZGUgZmVhdHVyZXMgbmV0dG95w6llcywgbm91cyBwb3V2b25zIHByb2PDqWRlciDDoCBsJ2FuYWx5c2Ugc3lzdMOpbWlxdWUgZGUgesOpcm8uIENyw6nDqW9ucyBkJ2Fib3JkIGxlcyBkaWZmw6lyZW50cyBqZXV4IGRlIGRvbm7DqWVzLg0KDQpgYGB7ciBHZW5lcmF0aW9uRG9ubmVlczJ9DQojIENvbXB0ZXVyIGRlIHRlbXBzDQpDdXJyZW50VGltZSA8LSB0aW1lcigpICMgUHLDqXBhcmF0aW9uIGRlIGwnw6l2YWx1YXRpb24gZHUgbW9kw6hsZSBuZXR0b3nDqQ0KDQojIE/DuSBzYXV2ZWdhcmRlciBsZXMgZmljaGllcnMgPw0KZmlsZV90YWcgPC0gIjJfZGF0YS8iDQoNCiMgSW5pdGlhbGlzYXRpb24gZGUgbGEgdmFyaWFibGUgcXVpIGFjY3VlaWxsZXJhIGxhIHByw6ljaXNpb24NCmFjY3VyYWN5IDwtIGRhdGEuZnJhbWUobWF0cml4KG5yb3cgPSAxNiwgbmNvbCA9IDEzKSkNCmNvbG5hbWVzKGFjY3VyYWN5KSA8LSBjKCJGb2xkIiwgInhnYl9MaW5lYXJNb2RlbCIsICJ4Z2JfRGVjaXNpb25UcmVlIiwgInhnYl9SYW5kb21Gb3Jlc3QiLCAieGdiX0dyYWRpZW50Qm9vc3RpbmciLCAiaDJvX0xpbmVhck1vZGVsIiwgImgyb19EZWNpc2lvblRyZWUiLCAiaDJvX1JhbmRvbUZvcmVzdCIsICJoMm9fR3JhZGllbnRCb29zdGluZyIsICJoMm9fTk5fMzJ4Nl9SZUxVIiwgImgyb19OTl8zMng2X1NvZnQiLCAiaDJvX05OXzE2eDE2eDZfUmVMVSIsICJoMm9fTk5fMTZ4MTZ4Nl9Tb2Z0IikNCmFjY3VyYWN5WywgMV0gPC0gYygiRm9sZF8xdjIiLCAiRm9sZF8xdjMiLCAiRm9sZF8ydjEiLCAiRm9sZF8ydjMiLCAiRm9sZF8zdjEiLCAiRm9sZF8zdjIiLCAiRm9sZF8xdjIzIiwgIkZvbGRfMnYxMyIsICJGb2xkXzN2MTIiLCAiRm9sZF8xMnYzIiwgIkZvbGRfMTN2MiIsICJGb2xkXzIzdjEiLCAiTW95ZW5uZV8xYzEiLCAiTW95ZW5uZV8xYzIiLCAiTW95ZW5uZV8yYzEiLCAiTW95ZW5uZSIpDQoNCiMgSW5pdGlhbGlzYXRpb24gZGVzIGZvbGRzIHBvdXIgbGEgY3Jvc3MtdmFsaWRhdGlvbg0KZm9sZHNfdHJhaW4gPC0gbGlzdCgpDQpmb2xkc190ZXN0IDwtIGxpc3QoKQ0KdHJhaW5pbmdfZGF0YSA8LSBsaXN0KCkNCnRlc3RpbmdfZGF0YSA8LSBsaXN0KCkNCnRyYWluaW5nX3hnYiA8LSBsaXN0KCkNCnRlc3RpbmdfeGdiIDwtIGxpc3QoKQ0KdHJhaW5pbmdfaDJvIDwtIGxpc3QoKQ0KdGVzdGluZ19oMm8gPC0gbGlzdCgpDQpjb21iaW5hdGlvbnNfdHJhaW4gPC0gYyhsaXN0KDEsIDEsIDIsIDIsIDMsIDMpLCBjb21ibigzLCAxLCBzaW1wbGlmeSA9IEZBTFNFKSwgY29tYm4oMywgMiwgc2ltcGxpZnkgPSBGQUxTRSkpDQpjb21iaW5hdGlvbnNfdGVzdCA8LSBjKGxpc3QoMiwgMywgMSwgMywgMSwgMiksIHJldihjb21ibigzLCAyLCBzaW1wbGlmeSA9IEZBTFNFKSksIHJldihjb21ibigzLCAxLCBzaW1wbGlmeSA9IEZBTFNFKSkpDQp0ZW1wX2ZhY3RvcnMgPC0gYXMuZmFjdG9yKGdyb3VwX3BhdGgkcGF0aF9JRCkNCg0KIyBDcsOpYXRpb24gZGVzIGRvbm7DqWVzIGQnZW50cmFpbmVtZW50IGV0IGRlIHZhbGlkYXRpb24NCmZvciAoaSBpbiAxOjEyKSB7DQogIA0KICAjIENyw6lhdGlvbiBkZXMgZm9sZHMgZCdlbnRyYWluZW1lbnQgZXQgZGUgdmFsaWRhdGlvbg0KICBmb2xkc190cmFpbltbaV1dIDwtIHdoaWNoKGdyb3VwX3Jvb21bWyJkYXRhc2V0X0lEIl1dICVpbiUgY29tYmluYXRpb25zX3RyYWluW1tpXV0pDQogIGZvbGRzX3Rlc3RbW2ldXSA8LSB3aGljaChncm91cF9yb29tW1siZGF0YXNldF9JRCJdXSAlaW4lIGNvbWJpbmF0aW9uc190ZXN0W1tpXV0pDQogIA0KICAjIFJlY2hlcmNoZSBldCBzdXBwcmVzc2lvbiBkdSBsYWJlbCAzIGxvcnNxdWUgbGEgc2FsbGUgMSBlc3QgaXNvbMOpZSAoc29pdCBlbiB0cmFpbiBvbiBlbmzDqHZlIGVuIHRlc3QsIHNvaXQgZW4gdGVzdCBvbiBlbmzDqHZlIGVuIHRyYWluKQ0KICBpZiAoKGxlbmd0aChjb21iaW5hdGlvbnNfdHJhaW5bW2ldXSkgPT0gMSkgJiAoY29tYmluYXRpb25zX3RyYWluW1tpXV1bMV0gPT0gMSkpIHsNCiAgICBmb2xkc190ZXN0W1tpXV0gPC0gZm9sZHNfdGVzdFtbaV1dW2dyb3VwX3BhdGgkcGF0aF9JRFtmb2xkc190ZXN0W1tpXV1dICE9IDNdDQogIH0NCiAgaWYgKChsZW5ndGgoY29tYmluYXRpb25zX3Rlc3RbW2ldXSkgPT0gMSkgJiAoY29tYmluYXRpb25zX3Rlc3RbW2ldXVsxXSA9PSAxKSkgew0KICAgIGZvbGRzX3RyYWluW1tpXV0gPC0gZm9sZHNfdHJhaW5bW2ldXVtncm91cF9wYXRoJHBhdGhfSURbZm9sZHNfdHJhaW5bW2ldXV0gIT0gM10NCiAgfQ0KICANCiAgIyBDcsOpYXRpb24gZGVzIGRvbm7DqWVzIGQnZW50cmFpbmVtZW50IGV0IGRlIHZhbGlkYXRpb24NCiAgdHJhaW5pbmdfZGF0YVtbaV1dIDwtIG1pbmlfbG1bZm9sZHNfdHJhaW5bW2ldXSwgXQ0KICB0ZXN0aW5nX2RhdGFbW2ldXSA8LSBtaW5pX2xtW2ZvbGRzX3Rlc3RbW2ldXSwgXQ0KICANCiAgIyBFbnJlZ2lzdHJlbWVudCBkZXMgZG9ubsOpZXMgQ1NWDQogIGZ3cml0ZSh0cmFpbmluZ19kYXRhW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRyYWluTkxfIiwgc3ByaW50ZigiJTAyZCIsIGkpLCAiLmNzdiIpKQ0KICBmd3JpdGUodGVzdGluZ19kYXRhW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRlc3ROTF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuY3N2IikpDQogIA0KICAjIFRyYW5zZm9ybWF0aW9uIGRlcyBkb25uw6llcyBhdSBmb3JtYXQgYXBwcm9wcmnDqSBwb3VyIHhnYm9vc3QNCiAgdHJhaW5pbmdfeGdiW1tpXV0gPC0geGdiLkRNYXRyaXgoZGF0YSA9IGFzLm1hdHJpeCh0cmFpbmluZ19kYXRhW1tpXV0pLCBsYWJlbCA9IGdyb3VwX3BhdGgkcGF0aF9JRFtmb2xkc190cmFpbltbaV1dXSAtIDEpDQogIHRlc3RpbmdfeGdiW1tpXV0gPC0geGdiLkRNYXRyaXgoZGF0YSA9IGFzLm1hdHJpeCh0ZXN0aW5nX2RhdGFbW2ldXSksIGxhYmVsID0gZ3JvdXBfcGF0aCRwYXRoX0lEW2ZvbGRzX3Rlc3RbW2ldXV0gLSAxKQ0KICANCiAgIyBEdW1waW5nIGRlcyBkYXRhc2V0cyBiaW5haXJlcyB4Z2Jvb3N0DQogIHhnYi5ETWF0cml4LnNhdmUodHJhaW5pbmdfeGdiW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRyYWluTF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuZGF0YSIpKQ0KICB4Z2IuRE1hdHJpeC5zYXZlKHRlc3RpbmdfeGdiW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRlc3RMXyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5kYXRhIikpDQogIA0KICAjIFRyYW5zZm9ybWF0aW9uIGRlcyBkb25uw6llcyBhdSBmb3JtYXQgYXBwcm9wcmnDqSBwb3VyIEgyTw0KICB0cmFpbmluZ19oMm9bW2ldXSA8LSBhcy5oMm8oY2JpbmQoTGFiZWwgPSB0ZW1wX2ZhY3RvcnNbZm9sZHNfdHJhaW5bW2ldXV0sIHRyYWluaW5nX2RhdGFbW2ldXSkpDQogIHRlc3RpbmdfaDJvW1tpXV0gPC0gYXMuaDJvKGNiaW5kKExhYmVsID0gdGVtcF9mYWN0b3JzW2ZvbGRzX3Rlc3RbW2ldXV0sIHRlc3RpbmdfZGF0YVtbaV1dKSkNCiAgDQogICMgRW5yZWdpc3RyZW1lbnQgZGVzIGZyYW1lcyBIMk8gKENTViArIExhYmVsKQ0KICBoMm8uZXhwb3J0RmlsZSh0cmFpbmluZ19oMm9bW2ldXSwgcGFzdGUwKGZpbGVfdGFnLCAidHJhaW5MXyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5jc3YiKSwgZm9yY2UgPSBUUlVFKQ0KICBoMm8uZXhwb3J0RmlsZSh0ZXN0aW5nX2gyb1tbaV1dLCBwYXN0ZTAoZmlsZV90YWcsICJ0ZXN0TF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuY3N2IiksIGZvcmNlID0gVFJVRSkNCiAgDQp9DQoNCiMgVGVtcHMgbsOpY2Vzc2FpcmUNCnRpbWluZyhDdXJyZW50VGltZSwgIlByw6lwYXJhdGlvbiBkZSBsJ8OpdmFsdWF0aW9uIGR1IG1vZMOobGUgbmV0dG95w6kiKQ0KYGBgDQoNCiMjIE5vdXZlbCBlbnRyYcOubmVtZW50IGRlcyBkb3V6ZSBtb2TDqGxlcw0KDQpOb3VzIHBvdXZvbnMgcmVsYW5jZXIgbGVzIGNhbGN1bHMgcG91ciBkw6l0ZXJtaW5lciBsYSBwZXJmb3JtYW5jZSBkZXMgbW9kw6hsZXMuIFVuIGVudHJhaW5lbWVudCBkZSB0b3VzIGxlcyBtb2TDqGxlcyBlc3QgYmllbiBzw7tyIG7DqWNlc3NhaXJlLCBhaW5zaSBxdWUgbGEgdmFsaWRhdGlvbiBkZSBsZXVycyBwZXJmb3JtYW5jZXMgc3VyIGRlcyDDqWNoYW50aWxsb25zICJpbmNvbm51cyIuDQoNCmBgYHtyIEVudHJhaW5lbWVudDIsIGNhY2hlPVRSVUV9DQojIENvbXB0ZXVyIGRlIHRlbXBzDQpDdXJyZW50VGltZSA8LSB0aW1lcigpICMgQ2h1bmsgQ3LDqWF0aW9uIGV0IMOpdmFsdWF0aW9uIGRlcyBkb3V6ZSBtb2TDqGxlcyBkZSBiZW5jaG1hcmsgbmV0dG95w6kNCg0KIyBPw7kgc2F1dmVnYXJkZXIgbGVzIGZpY2hpZXJzID8NCmZpbGVfdGFnIDwtICIyX21vZGVscy8iDQpmaWxlX2gybyA8LSAiMl9tb2RlbHMiDQoNCiMgQm91Y2xlIGQnw6l2YWx1YXRpb24NCmZvciAoaSBpbiAxOjEyKSB7DQogIA0KICAjIEVudHJhaW5lbWVudCBkdSBtb2TDqGxlIGRlIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUgKHhnYm9vc3QpDQogIHRlbXBfbW9kZWwgPC0geGdiX2R5bmFtaWNfdHJhaW4odHJhaW4gPSB0cmFpbmluZ194Z2JbW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdGluZ194Z2JbW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib29zdGVyID0gImdibGluZWFyIiwgIyBMaW7DqWFpcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBucm91bmRzID0gMTAwMDAwMCwgIyBBcnLDqnTDqSBhdSBtZWlsbGV1ciByw6lzdWx0YXQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1fcGFyYWxsZWxfdHJlZXMgPSAxKQ0KICB4Z2IuZHVtcChtb2RlbCA9IHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICBmbmFtZSA9IHBhc3RlMChmaWxlX3RhZywgInhnYl9nbG1fIiwgc3ByaW50ZigiJTAyZCIsIGkpLCAiLmpzb24iKSwgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgIHdpdGhfc3RhdHMgPSBUUlVFLCAjIEVucmVnaXN0cmVtZW50IGRlcyBzdGF0aXN0aXF1ZXMgc2kgbW9kw6hsZSBnYnRyZWUNCiAgICAgICAgICAgZHVtcF9mb3JtYXQgPSAianNvbiIpICMgRHVtcCBhdSBmb3JtYXQganNvbiwgcsOpLXV0aWxpc2FibGUNCiAgYWNjdXJhY3lbaSwgMl0gPC0gMSAtIHRlbXBfbW9kZWwkZXZhbHVhdGlvbl9sb2dbWzJdXVt0ZW1wX21vZGVsJGJlc3RfaXRlcmF0aW9uXSAjIFLDqWN1cMOpcmF0aW9uIGR1IG1laWxsZXVyIHLDqXN1bHRhdA0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbW9kw6hsZSBkJ2FyYnJlIGRlIGTDqWNpc2lvbiAoeGdib29zdCkNCiAgdGVtcF9tb2RlbCA8LSB4Z2JfZHluYW1pY190cmFpbih0cmFpbiA9IHRyYWluaW5nX3hnYltbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSB0ZXN0aW5nX3hnYltbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvb3N0ZXIgPSAiZ2J0cmVlIiwgIyBOb24tbGluw6lhaXJlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdW5kcyA9IDEsICMgVW4gc2V1bCBhcmJyZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG51bV9wYXJhbGxlbF90cmVlcyA9IDEpDQogIHhnYi5kdW1wKG1vZGVsID0gdGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgIGZuYW1lID0gcGFzdGUwKGZpbGVfdGFnLCAieGdiX2R0XyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5qc29uIiksICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICB3aXRoX3N0YXRzID0gVFJVRSwgIyBFbnJlZ2lzdHJlbWVudCBkZXMgc3RhdGlzdGlxdWVzIHNpIG1vZMOobGUgZ2J0cmVlDQogICAgICAgICAgIGR1bXBfZm9ybWF0ID0gImpzb24iKSAjIER1bXAgYXUgZm9ybWF0IGpzb24sIHLDqS11dGlsaXNhYmxlDQogIGFjY3VyYWN5W2ksIDNdIDwtIDEgLSB0ZW1wX21vZGVsJGV2YWx1YXRpb25fbG9nW1syXV1bMV0gIyBSw6ljdXDDqXJhdGlvbiBkdSBtZWlsbGV1ciByw6lzdWx0YXQNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IG1vZMOobGUgZGUgUmFuZG9tIEZvcmVzdCAoeGdib29zdCkNCiAgdGVtcF9tb2RlbCA8LSB4Z2JfZHluYW1pY190cmFpbih0cmFpbiA9IHRyYWluaW5nX3hnYltbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSB0ZXN0aW5nX3hnYltbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvb3N0ZXIgPSAiZ2J0cmVlIiwgIyBOb24tbGluw6lhaXJlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdW5kcyA9IDEsICMgVW5lIHNldWxlIGl0w6lyYXRpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1fcGFyYWxsZWxfdHJlZXMgPSAyMDApICMgRGUgMjAwIGFyYnJlcw0KICB4Z2IuZHVtcChtb2RlbCA9IHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICBmbmFtZSA9IHBhc3RlMChmaWxlX3RhZywgInhnYl9yZl8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuanNvbiIpLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgd2l0aF9zdGF0cyA9IFRSVUUsICMgRW5yZWdpc3RyZW1lbnQgZGVzIHN0YXRpc3RpcXVlcyBzaSBtb2TDqGxlIGdidHJlZQ0KICAgICAgICAgICBkdW1wX2Zvcm1hdCA9ICJqc29uIikgIyBEdW1wIGF1IGZvcm1hdCBqc29uLCByw6ktdXRpbGlzYWJsZQ0KICBhY2N1cmFjeVtpLCA0XSA8LSAxIC0gdGVtcF9tb2RlbCRldmFsdWF0aW9uX2xvZ1tbMl1dICMgUsOpY3Vww6lyYXRpb24gZHUgbWVpbGxldXIgcsOpc3VsdGF0DQogIA0KICAjIEVudHJhaW5lbWVudCBkdSBtb2TDqGxlIGQnYXJicmUgZGUgZMOpY2lzaW9uIGJvb3N0w6kgYXZlYyBwcm90ZWN0aW9uIGNvbnRyZSBsJ292ZXJmaXR0aW5nICh4Z2Jvb3N0KQ0KICB0ZW1wX21vZGVsIDwtIHhnYl9keW5hbWljX3RyYWluKHRyYWluID0gdHJhaW5pbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IHRlc3RpbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9vc3RlciA9ICJnYnRyZWUiLCAjIE5vbi1saW7DqWFpcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBucm91bmRzID0gMTAwMDAwMCwgIyBBcnLDqnTDqSBhdSBtZWlsbGV1ciByw6lzdWx0YXQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1fcGFyYWxsZWxfdHJlZXMgPSAxKQ0KICB4Z2IuZHVtcChtb2RlbCA9IHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICBmbmFtZSA9IHBhc3RlMChmaWxlX3RhZywgInhnYl9nYnRfIiwgc3ByaW50ZigiJTAyZCIsIGkpLCAiLmpzb24iKSwgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgIHdpdGhfc3RhdHMgPSBUUlVFLCAjIEVucmVnaXN0cmVtZW50IGRlcyBzdGF0aXN0aXF1ZXMgc2kgbW9kw6hsZSBnYnRyZWUNCiAgICAgICAgICAgZHVtcF9mb3JtYXQgPSAianNvbiIpICMgRHVtcCBhdSBmb3JtYXQganNvbiwgcsOpLXV0aWxpc2FibGUNCiAgYWNjdXJhY3lbaSwgNV0gPC0gMSAtIHRlbXBfbW9kZWwkZXZhbHVhdGlvbl9sb2dbWzJdXVt0ZW1wX21vZGVsJGJlc3RfaXRlcmF0aW9uXSAjIFLDqWN1cMOpcmF0aW9uIGR1IG1laWxsZXVyIHLDqXN1bHRhdA0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbW9kw6hsZSBkZSByw6lncmVzc2lvbiBsb2dpc3RpcXVlIChoMm8pDQogIHRlbXBfbW9kZWwgPC0gaDJvLmdsbSh5ID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluaW5nX2ZyYW1lID0gdHJhaW5pbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICB2YWxpZGF0aW9uX2ZyYW1lID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2lkID0gcGFzdGUwKCJoMm9fZ2xtXyIsIHNwcmludGYoIiUwMmQiLCBpKSksICMgTm9tIGR1IG1vZMOobGUNCiAgICAgICAgICAgICAgICAgICAgICAgIG1heF9pdGVyYXRpb25zID0gMTAwLCAjIDEwMCBpdMOpcmF0aW9ucyBkJ29wdGltaXNhdGlvbg0KICAgICAgICAgICAgICAgICAgICAgICAgc29sdmVyID0gIklSTFNNIiwgIyBTb2x2ZXVyIHBhciBkw6lmYXV0DQogICAgICAgICAgICAgICAgICAgICAgICBzdGFuZGFyZGl6ZSA9IEZBTFNFLCAjIFBhcyBkZSBzdGFuZGFyZGlzYXRpb24gcHVpc3F1ZSBbLTEsIDFdDQogICAgICAgICAgICAgICAgICAgICAgICBmYW1pbHkgPSAibXVsdGlub21pYWwiLCAjIENsYXNzaWZpY2F0aW9uIG11bHRpLWNsYXNzZQ0KICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDAsICMgUmVwcm9kdWN0aW9uIGRlcyByw6lzdWx0YXRzDQogICAgICAgICAgICAgICAgICAgICAgICBpbnRlcmNlcHQgPSBUUlVFKQ0KICBoMm8uZG93bmxvYWRfcG9qbyh0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgICAgICAgICAgcGF0aCA9IGZpbGVfaDJvLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgICAgICAgICAgZ2V0X2phciA9IEZBTFNFKSAjIFBhcyBkZSBmaWNoaWVyIC5qYXINCiAgYWNjdXJhY3lbaSwgNl0gPC0gdGVtcF9tb2RlbEBtb2RlbCR2YWxpZGF0aW9uX21ldHJpY3NAbWV0cmljcyRoaXRfcmF0aW9fdGFibGVbMSwgMl0NCiAgDQogICMgRW50cmFpbmVtZW50IGR1IG1vZMOobGUgZCdhcmJyZSBkZSBkw6ljaXNpb24gKGgybykNCiAgdGVtcF9tb2RlbCA8LSBoMm8ucmFuZG9tRm9yZXN0KHkgPSAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbmluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbGlkYXRpb25fZnJhbWUgPSB0ZXN0aW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgPSBwYXN0ZTAoImgyb19kdF8iLCBzcHJpbnRmKCIlMDJkIiwgaSkpLCAjIE5vbSBkdSBtb2TDqGxlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVfcmF0ZSA9IDEsICMgVG91dGVzIGxlcyBvYnNlcnZhdGlvbnMgc2Vyb250IHByaXNlcyBlbiBjb21wdGUgcG91ciBsZSBzZXVsIGFyYnJlIGRlIGTDqWNpc2lvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXRyaWVzID0gMzYsICMgVG91dGVzIGxlcyBmZWF0dXJlcyBzZXJvbnQgcHJpc2VzIGVuIGNvbXB0ZSBwb3VyIGxlIHNldWwgYXJicmUgZGUgZMOpY2lzaW9uDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZXMgPSAxLCAjIFVuIHNldWwgYXJicmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSAwKSAjIFJlcHJvZHVjdGlvbiBkZXMgcsOpc3VsdGF0cw0KICBoMm8uZG93bmxvYWRfcG9qbyh0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgICAgICAgICAgcGF0aCA9IGZpbGVfaDJvLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgICAgICAgICAgZ2V0X2phciA9IEZBTFNFKSAjIFBhcyBkZSBmaWNoaWVyIC5qYXINCiAgYWNjdXJhY3lbaSwgN10gPC0gMSAtIG1pbih0ZW1wX21vZGVsQG1vZGVsJHNjb3JpbmdfaGlzdG9yeSR2YWxpZGF0aW9uX2NsYXNzaWZpY2F0aW9uX2Vycm9yLCBuYS5ybSA9IFRSVUUpDQogIA0KICAjIEVudHJhaW5lbWVudCBkdSBtb2TDqGxlIGRlIFJhbmRvbSBGb3Jlc3QgKGgybykNCiAgdGVtcF9tb2RlbCA8LSBoMm8ucmFuZG9tRm9yZXN0KHkgPSAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbmluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbGlkYXRpb25fZnJhbWUgPSB0ZXN0aW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgPSBwYXN0ZTAoImgyb19yZl8iLCBzcHJpbnRmKCIlMDJkIiwgaSkpLCAjIE5vbSBkdSBtb2TDqGxlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVfcmF0ZSA9IDAuNjMyLCAjIEJvb3RzdHJhcHBpbmcgLjYzMiBwb3VyIGNoYXF1ZSBhcmJyZSBkZSBkw6ljaXNpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG10cmllcyA9IC0xLCAjIHNxcnQoMzYpIGZlYXR1cmVzIHNlcm9udCBwcmlzZXMgZW4gY29tcHRlIHBvdXIgY2hhcXVlIGFyYnJlIGRlIGTDqWNpc2lvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnRyZWVzID0gMjAwLCAjIDIwMCBhcmJyZXMNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSAwKSAjIFJlcHJvZHVjdGlvbiBkZXMgcsOpc3VsdGF0cw0KICBoMm8uZG93bmxvYWRfcG9qbyh0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgICAgICAgICAgcGF0aCA9IGZpbGVfaDJvLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgICAgICAgICAgZ2V0X2phciA9IEZBTFNFKSAjIFBhcyBkZSBmaWNoaWVyIC5qYXINCiAgYWNjdXJhY3lbaSwgOF0gPC0gMSAtIG1pbih0ZW1wX21vZGVsQG1vZGVsJHNjb3JpbmdfaGlzdG9yeSR2YWxpZGF0aW9uX2NsYXNzaWZpY2F0aW9uX2Vycm9yLCBuYS5ybSA9IFRSVUUpDQogIA0KICAjIEVudHJhaW5lbWVudCBkdSBtb2TDqGxlIGQnYXJicmUgZGUgZMOpY2lzaW9uIGJvb3N0w6kgYXZlYyBwcm90ZWN0aW9uIGNvbnRyZSBsJ292ZXJmaXR0aW5nIChoMm8pDQogIHRlbXBfbW9kZWwgPC0gaDJvLmdibSh5ID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICB0cmFpbmluZ19mcmFtZSA9IHRyYWluaW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgIHZhbGlkYXRpb25fZnJhbWUgPSB0ZXN0aW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2lkID0gcGFzdGUwKCJoMm9fZ2J0XyIsIHNwcmludGYoIiUwMmQiLCBpKSksICMgTm9tIGR1IG1vZMOobGUNCiAgICAgICAgICAgICAgICAgICAgICBkaXN0cmlidXRpb24gPSAibXVsdGlub21pYWwiLCAjIENsYXNzaWZpY2F0aW9uIG11bHRpLWNsYXNzZQ0KICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZV9yYXRlID0gMSwgIyBQYXMgZGUgcHJvY2Vzc3VzIHN0b2NoYXN0aXF1ZQ0KICAgICAgICAgICAgICAgICAgICAgIG50cmVlcyA9IDEwMCwgIyAxMDAgaXTDqXJhdGlvbnMgZGUgYm9vc3RpbmcgYXUgbWF4aW11bQ0KICAgICAgICAgICAgICAgICAgICAgIHNjb3JlX2VhY2hfaXRlcmF0aW9uID0gVFJVRSwgIyBOb3RlciBsYSB2YWxldXIgZGUgY2hhcXVlIGl0w6lyYXRpb24NCiAgICAgICAgICAgICAgICAgICAgICBzdG9wcGluZ19yb3VuZHMgPSAxMCwgIyBBcnLDqnQgYXByw6hzIDEwIGl0w6lyYXRpb25zIHNhbnMgYW3DqWxpb3JhdG9uIGRlIGxhIG3DqXRyaXF1ZQ0KICAgICAgICAgICAgICAgICAgICAgIHN0b3BwaW5nX21ldHJpYyA9ICJtaXNjbGFzc2lmaWNhdGlvbiIsICMgU3VydmVpbGxlciBsJ2luZXhhY3RpdHVkZSBkZSBsYSBjbGFzc2lmaWNhdGlvbiBwb3VyIGwnYXJyw6p0DQogICAgICAgICAgICAgICAgICAgICAgc3RvcHBpbmdfdG9sZXJhbmNlID0gMC4wMDAwMSwgIyBBcnLDqnRlciBsb3JzcXVlIGxhIG3DqXRyaXF1ZSBzdGFnbmUgZGUgMC4wMDElDQogICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDApICMgUmVwcm9kdWN0aW9uIGRlcyByw6lzdWx0YXRzDQogIGgyby5kb3dubG9hZF9wb2pvKHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICAgICAgICAgICBwYXRoID0gZmlsZV9oMm8sICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICAgICAgICAgICBnZXRfamFyID0gRkFMU0UpICMgUGFzIGRlIGZpY2hpZXIgLmphcg0KICBhY2N1cmFjeVtpLCA5XSA8LSAxIC0gbWluKHRlbXBfbW9kZWxAbW9kZWwkc2NvcmluZ19oaXN0b3J5JHZhbGlkYXRpb25fY2xhc3NpZmljYXRpb25fZXJyb3IsIG5hLnJtID0gVFJVRSkNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IHLDqXNlYXUgZGUgbmV1cm9uZXMgw6AgYXJjaGl0ZWN0dXJlIDMyeDYgKyBSZUxVIChoMm8pDQogIHRlbXBfbW9kZWwgPC0gaDJvX25uX3RyYWluKHRyYWluID0gdHJhaW5pbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSB0ZXN0aW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9pZCA9IHBhc3RlMCgiaDJvX25uXzMyeDZfUmVMVV8iLCBzcHJpbnRmKCIlMDJkIiwgaSkpLCAjIE5vbSBkdSBtb2TDqGxlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFjdGl2YXRpb24gPSAiUmVjdGlmaWVyIiwgIyBSZUxVDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhpZGRlbiA9IDMyKSAjIEFyY2hpdGVjdHVyZSAzMng2DQogIGgyby5kb3dubG9hZF9wb2pvKHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICAgICAgICAgICBwYXRoID0gZmlsZV9oMm8sICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICAgICAgICAgICBnZXRfamFyID0gRkFMU0UpICMgUGFzIGRlIGZpY2hpZXIgLmphcg0KICBhY2N1cmFjeVtpLCAxMF0gPC0gMSAtIG1pbih0ZW1wX21vZGVsQG1vZGVsJHNjb3JpbmdfaGlzdG9yeSR2YWxpZGF0aW9uX2NsYXNzaWZpY2F0aW9uX2Vycm9yLCBuYS5ybSA9IFRSVUUpDQogIA0KICAjIEVudHJhaW5lbWVudCBkdSByw6lzZWF1IGRlIG5ldXJvbmVzIMOgIGFyY2hpdGVjdHVyZSAzMng2ICsgVGFuaCAoaDJvKQ0KICB0ZW1wX21vZGVsIDwtIGgyb19ubl90cmFpbih0cmFpbiA9IHRyYWluaW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgPSBwYXN0ZTAoImgyb19ubl8zMng2X1RhbmhfIiwgc3ByaW50ZigiJTAyZCIsIGkpKSwgIyBOb20gZHUgbW9kw6hsZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhY3RpdmF0aW9uID0gIlRhbmgiLCAjICJTaWdtb2lkZSINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGlkZGVuID0gMzIpICMgQXJjaGl0ZWN0dXJlIDMyeDYNCiAgaDJvLmRvd25sb2FkX3Bvam8odGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgICAgICAgICAgIHBhdGggPSBmaWxlX2gybywgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgICAgICAgICAgIGdldF9qYXIgPSBGQUxTRSkgIyBQYXMgZGUgZmljaGllciAuamFyDQogIGFjY3VyYWN5W2ksIDExXSA8LSAxIC0gbWluKHRlbXBfbW9kZWxAbW9kZWwkc2NvcmluZ19oaXN0b3J5JHZhbGlkYXRpb25fY2xhc3NpZmljYXRpb25fZXJyb3IsIG5hLnJtID0gVFJVRSkNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IHLDqXNlYXUgZGUgbmV1cm9uZXMgw6AgYXJjaGl0ZWN0dXJlIDE2eDE2eDYgKyBSZUxVIChoMm8pDQogIHRlbXBfbW9kZWwgPC0gaDJvX25uX3RyYWluKHRyYWluID0gdHJhaW5pbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSB0ZXN0aW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9pZCA9IHBhc3RlMCgiaDJvX25uXzE2eDE2eDZfUmVMVV8iLCBzcHJpbnRmKCIlMDJkIiwgaSkpLCAjIE5vbSBkdSBtb2TDqGxlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFjdGl2YXRpb24gPSAiUmVjdGlmaWVyIiwgIyBSZUxVDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhpZGRlbiA9IGMoMTYsIDE2KSkgIyBBcmNoaXRlY3R1cmUgMTZ4MTZ4Ng0KICBoMm8uZG93bmxvYWRfcG9qbyh0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgICAgICAgICAgcGF0aCA9IGZpbGVfaDJvLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgICAgICAgICAgZ2V0X2phciA9IEZBTFNFKSAjIFBhcyBkZSBmaWNoaWVyIC5qYXINCiAgYWNjdXJhY3lbaSwgMTJdIDwtIDEgLSBtaW4odGVtcF9tb2RlbEBtb2RlbCRzY29yaW5nX2hpc3RvcnkkdmFsaWRhdGlvbl9jbGFzc2lmaWNhdGlvbl9lcnJvciwgbmEucm0gPSBUUlVFKQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgcsOpc2VhdSBkZSBuZXVyb25lcyDDoCBhcmNoaXRlY3R1cmUgMTZ4MTZ4NiArIFRhbmggKGgybykNCiAgdGVtcF9tb2RlbCA8LSBoMm9fbm5fdHJhaW4odHJhaW4gPSB0cmFpbmluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IHRlc3RpbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2lkID0gcGFzdGUwKCJoMm9fbm5fMTZ4MTZ4Nl9UYW5oXyIsIHNwcmludGYoIiUwMmQiLCBpKSksICMgTm9tIGR1IG1vZMOobGUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWN0aXZhdGlvbiA9ICJUYW5oIiwgIyAiU2lnbW9pZGUiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhpZGRlbiA9IGMoMTYsIDE2KSkgIyBBcmNoaXRlY3R1cmUgMTZ4MTZ4Ng0KICBoMm8uZG93bmxvYWRfcG9qbyh0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgICAgICAgICAgcGF0aCA9IGZpbGVfaDJvLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgICAgICAgICAgZ2V0X2phciA9IEZBTFNFKSAjIFBhcyBkZSBmaWNoaWVyIC5qYXINCiAgYWNjdXJhY3lbaSwgMTNdIDwtIDEgLSBtaW4odGVtcF9tb2RlbEBtb2RlbCRzY29yaW5nX2hpc3RvcnkkdmFsaWRhdGlvbl9jbGFzc2lmaWNhdGlvbl9lcnJvciwgbmEucm0gPSBUUlVFKQ0KICANCn0NCg0KIyBUZW1wcyBuw6ljZXNzYWlyZQ0KdGltaW5nKEN1cnJlbnRUaW1lLCAiQ3LDqWF0aW9uIGV0IMOpdmFsdWF0aW9uIGRlcyBkb3V6ZSBtb2TDqGxlcyBkZSBiZW5jaG1hcmsgbmV0dG95w6kiKQ0KYGBgDQoNCiMjIE5vdXZlbGxlIGFuYWx5c2UgZGVzIHLDqXN1bHRhdHMNCg0KTm91cyB2b3lvbnMgY2xhaXJlbWVudCBxdWUgbGVzIG1vZMOobGVzIGxpbsOpYWlyZXMgc2UgZMOpbWFycXVlbnQgZGUgdG91cyBsZXMgYXV0cmVzIG1vZMOobGVzIG5vbi1saW7DqWFpcmVzLiBJbCB5IGEgZXUgdW5lIHJhaXNvbiB0b3V0ZSBzaW1wbGUgw6AgYSBjZWxhIDogbGVzIG1vZMOobGVzIG5vbi1saW7DqWFpcmVzIHV0aWxpc8OpcyBuZSBwZXV2ZW50IHMnYWNjb21vZGVyIGZhY2Ugw6AgZGUgbm91dmVsbGVzIHZhbGV1cnMgZGUgbWFuacOocmUgZWNoZWxvbm7DqWUsIGNlIHF1aSBlc3QgdG91dCBsZSBjb250cmFpcmUgZGVzIG1vZMOobGVzIGxpbsOpYWlyZXMgIQ0KDQpgYGB7ciBSZXN1bHRhdHMyfQ0KIyBNb3llbm5lIGRlcyByw6lzdWx0YXRzDQpmb3IgKGkgaW4gMjoxMykgew0KICBhY2N1cmFjeVsxMywgaV0gPC0gbWVhbihhY2N1cmFjeVsxOjYsIGldKQ0KICBhY2N1cmFjeVsxNCwgaV0gPC0gbWVhbihhY2N1cmFjeVs3OjksIGldKQ0KICBhY2N1cmFjeVsxNSwgaV0gPC0gbWVhbihhY2N1cmFjeVsxMDoxMiwgaV0pDQogIGFjY3VyYWN5WzE2LCBpXSA8LSBtZWFuKGFjY3VyYWN5WzEzOjE1LCBpXSkNCn0NCg0KIyBFbnJlZ2lzdHJlbWVudCBkZXMgc2NvcmVzDQpmd3JpdGUoYWNjdXJhY3ksICJzY29yZXMvMl9tb2RlbHMuY3N2IikNCg0KIyBBZmZpY2hhZ2UgZGVzIHLDqXN1bHRhdHMgZGFucyB1biB0YWJsZWF1IGludGVyYWN0aWYNCnRvX3ByaW50IDwtIGRhdGEudGFibGUodChhY2N1cmFjeVsxMzoxNiwgLTFdKSkgIyBQcsOpcGFyYXRpb24gZGVzIGRvbm7DqWVzIMOgIG1ldHRyZSBzdXIgdGFibGUNCmNvbG5hbWVzKHRvX3ByaW50KSA8LSBjKCIxIGNvbnRyZSAxIiwgIjEgY29udHJlIDIiLCAiMiBjb250cmUgMSIsICJNb3llbm5lIikgIyBSZW1pc2UgZGVzIG5vbXMgZGVzIGNvbG9ubmVzDQpyb3cubmFtZXModG9fcHJpbnQpIDwtIGNvbG5hbWVzKGFjY3VyYWN5KVstMV0gIyBSZW1pc2UgZGVzIG5vbXMgZGVzIGxpZ25lcw0KZGF0YXRhYmxlKHRvX3ByaW50LA0KICAgICAgICAgIGZpbHRlciA9ICJ0b3AiLCAjIEZpbHRyYWdlIGF1LWRlc3N1cyBkZSBsYSB0YWJsZQ0KICAgICAgICAgIGNsYXNzID0gImNlbGwtYm9yZGVyIHN0cmlwZSIsICMgQ1NTDQogICAgICAgICAgZXh0ZW5zaW9ucyA9IGMoIkNvbFJlb3JkZXIiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICJSb3dSZW9yZGVyIiksICMgUmVvcmRvbm5lciBtYW51ZWxsZW1lbnQgw6AgbGEgbWFpbg0KICAgICAgICAgIG9wdGlvbnMgPSBsaXN0KHBhZ2VMZW5ndGggPSAxMiwgIyBQYWdlIGFmZmljaGFudCAxMiBsaWduZXMNCiAgICAgICAgICAgICAgICAgICAgICAgICBvcmRlciA9IGxpc3QobGlzdCg0LCAiZGVzYyIpKSwgIyBPcmRvbm5lciBwYXIgZMOpZmF1dCBwYXIgbCdleGFjdGl0dWRlIG1veWVubmUNCiAgICAgICAgICAgICAgICAgICAgICAgICBjb2xSZW9yZGVyID0gVFJVRSwgIyBQbHVnaW4NCiAgICAgICAgICAgICAgICAgICAgICAgICByb3dSZW9yZGVyID0gVFJVRSkpICU+JSAjIFBsdWdpbg0KICBmb3JtYXRTdHlsZShjKCIxIGNvbnRyZSAxIiwgIjEgY29udHJlIDIiLCAiMiBjb250cmUgMSIpLA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IHN0eWxlQ29sb3JCYXIoYygwLCAxKSwgJ2xpZ2h0Z3JlZW4nKSwgIyBDb3VsZXVyIHZlcnQgY2xhaXIgcG91ciBsZXMgbcOpdHJpcXVlcyBwYXIgZm9sZA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZFNpemUgPSAnMTAwJSA5MCUnLA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZFJlcGVhdCA9ICduby1yZXBlYXQnLA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZFBvc2l0aW9uID0gJ2NlbnRlcicpICU+JQ0KICBmb3JtYXRTdHlsZSgiTW95ZW5uZSIsDQogICAgICAgICAgICAgIGJhY2tncm91bmQgPSBzdHlsZUNvbG9yQmFyKGMoMCwgMSksICdwaW5rJyksICMgQ291bGV1ciByb3NlIHBvdXIgbGEgbcOpdHJpcXVlIGRlIG1veWVubmUNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFNpemUgPSAnMTAwJSA5MCUnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgIGJhY2tncm91bmRQb3NpdGlvbiA9ICdjZW50ZXInKSAlPiUNCiAgZm9ybWF0UGVyY2VudGFnZShjb2x1bW5zID0gYygiMSBjb250cmUgMSIsICIxIGNvbnRyZSAyIiwgIjIgY29udHJlIDEiKSwNCiAgICAgICAgICAgICAgZGlnaXRzID0gOCkgJT4lDQogIGZvcm1hdFBlcmNlbnRhZ2UoY29sdW1ucyA9ICJNb3llbm5lIiwNCiAgICAgICAgICAgICAgZGlnaXRzID0gOCkNCg0KIyBBZmZpY2hhZ2UgZGVzIHLDqXN1bHRhdHMgZGFucyB1biB0YWJsZWF1IHN0YXRpcXVlDQpmb3JtYXR0YWJsZShhY2N1cmFjeVssIGMoMSwgMjo1KV0sIGxpc3QoZm9ybWF0dGFibGU6OmFyZWEoY29sID0geGdiX0xpbmVhck1vZGVsOnhnYl9HcmFkaWVudEJvb3N0aW5nKSB+IGNvbG9yX2Jhcigib3JhbmdlIikpKQ0KZm9ybWF0dGFibGUoYWNjdXJhY3lbLCBjKDEsIDY6OSldLCBsaXN0KGZvcm1hdHRhYmxlOjphcmVhKGNvbCA9IGgyb19MaW5lYXJNb2RlbDpoMm9fR3JhZGllbnRCb29zdGluZykgfiBjb2xvcl9iYXIoImN5YW4iKSkpDQpmb3JtYXR0YWJsZShhY2N1cmFjeVssIGMoMSwgMTA6MTMpXSwgbGlzdChmb3JtYXR0YWJsZTo6YXJlYShjb2wgPSBoMm9fTk5fMzJ4Nl9SZUxVOmgyb19OTl8xNngxNng2X1NvZnQpIH4gY29sb3JfYmFyKCJ5ZWxsb3ciKSkpDQpgYGANCg0KIyMgRMOpbW9uc3RyYXRpb24gZHUgcHJvYmzDqG1lIGxpbsOpYWlyZQ0KDQpJbCBlc3QgdHJhdmlhbCBkZSBkw6ltb250cmVyIHF1J3VuZSBwYXJ0aWUgZHUgcHJvYmzDqG1lIG5lIHBldXQgw6p0cmUgcsOpc29sdWUgZGUgbWFuacOocmUgbm9uLWxpbsOpYWlyZSBlbiBsJ8OpdGF0IHNhbnMgdW5lIGFuYWx5c2UgYXBwcm9mb25kaWUuIExlcyByw6lzaWR1cywgcGFyIGV4ZW1wbGUsIG9udCB1bmUgdmFsZXVyIG1veWVubmUgYmllbiBwbHVzIGdyYW5kZSBkYW5zIGxhIHNhbGxlIDMgKHBsdXMgZGlmZmljaWxlIMOgIHByw6l2b2lyKSBxdWUgbGVzIGRldXggYXV0cmVzIHNhbGxlcy4NCg0KUGFyIGV4ZW1wbGUsIGxlcyByw6lzaWR1cyBtb250cmVudCBxdWUgbGEgc2FsbGUgMSBlc3Qgw6AgZGlzc29jaWVyIGRlcyBkZXV4IGF1dHJlcyBzYWxsZXMuIENlbGEgZXN0IHbDqXJpZmlhYmxlIGVuIGVzdGltYW50IGxhIGRpZmbDqXJlbmNlIGRlcyBmZWF0dXJlcyBlbnRyZSBsYSBzYWxsZSAxIGV0IGxlcyBkZXV4IGF1dHJlcyBzYWxsZXMuIFBhciBzw6ljdXJpdMOpLCBvbiB1dGlsaXNlcmEgbGUgdGVzdCBVIGRlIE1hbm4tV2hpdG5leSDDoCBkZXV4IGJvcm5lcywgYXZlYyBjYWxjdWwgZXhhY3QgKHNhbnMgY29ycmVjdGlvbiBkZSBjb250aW51aXTDqSkuIFNpIGxhIHAtdmFsdWUgZXN0IHN1cMOpcmlldXJlIMOgIDAuMDUgKHNpIGwnb24gc3VwcG9zZSBub3RyZSBzZXVpbCBkZSBkw6ljaXNpb24gw6AgNSUgZCdlcnJldXIpLCBsZSB0ZXN0IG5lIHJlamV0dGUgcGFzIGwnaHlwb3Row6hzZSBudWxsZSAoZGlmZsOpcmVuY2UgZGVzIG3DqWRpYW5lcyDDqWdhbGUgw6AgesOpcm8pLiBEYW5zIGxlIGNhcyBjb250cmFpcmUsIGxhIGRpZmbDqXJlbmNlIGRlcyBtw6lkaWFuZXMgZXN0IHN0YXRpc3RpcXVlbWVudCBzdXDDqXJpZXVyZSDDoCAwIChsZXMgZGV1eCDDqWNoYW50aWxsb25zLCBsJ3VuZSBkYW5zIGxhIHNhbGxlIDEsIGwnYXV0cmUgZGFucyBsZXMgZGV1eCBhdXRyZXMgc2FsbGVzLCBzb250IHN0YXRpc3RpcXVlbWVudCBkaWZmw6lyZW50cykuIE9uIGFmZmljaGVyYSBsJ2ludGVydmFsbGUgZGUgY29uZmlhbmNlIMOgIDk1JSBkZXMgbcOpZGlhbmVzLg0KDQpgYGB7ciBMaW5lYXJpdGUsIGNhY2hlPVRSVUUsIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTEyfQ0KIyBDb21wdGV1ciBkZSB0ZW1wcw0KQ3VycmVudFRpbWUgPC0gdGltZXIoKSAjIFByw6lwYXJhdGlvbiBkZSBsJ8OpdmFsdWF0aW9uIGR1IG1vZMOobGUgbmV0dG95w6kNCg0KIyBQcsOpLWluaXRpYWxpc2F0aW9uIGRlcyB2YXJpYWJsZXMNCnRlbXBfdXRlc3QgPC0gZGF0YS5mcmFtZShGZWF0dXJlID0gYyhwYXN0ZTAocmVwKGMocGFzdGUwKCJDb2VmIiwgMTo0KSwgcGFzdGUwKCJSw6lzaSIsIDE6NCkpLCA0KSwgcGFzdGUwKCJfIiwgaW52ZXJzZS5ybGUobGlzdChsZW5ndGhzID0gcmVwKDgsIDQpLCB2YWx1ZXMgPSAxOjQpKSkpLCBwYXN0ZTAoIlBvc0luaXRpYWxlXyIsIDE6NCkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHBfdmFsdWUgPSBudW1lcmljKDM2KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBtZWRpYW5fZXN0ID0gbnVtZXJpYygzNiksDQogICAgICAgICAgICAgICAgICAgICAgICAgbWVkaWFuX2luZjk1ID0gbnVtZXJpYygzNiksDQogICAgICAgICAgICAgICAgICAgICAgICAgbWVkaWFuX3N1cDk1ID0gbnVtZXJpYygzNikpDQoNCiMgTWFubi1XaGl0bmV5IGV4YWN0IHR3by10YWlsZWQgVS10ZXN0DQpmb3IgKGkgaW4gMTozNikgew0KICB0ZW1wX3doaXRuZXkgPC0gd2lsY294LnRlc3QobWluaV9sbVtbaV1dW2dyb3VwX3BhdGgkcGF0aF9JRCA9PSAxXSwgbWluaV9sbVtbaV1dW2dyb3VwX3BhdGgkcGF0aF9JRCAhPSAxXSwgYWx0ZXJuYXRpdmUgPSAidHdvLnNpZGVkIiwgcGFpcmVkID0gRkFMU0UsIGV4YWN0ID0gVFJVRSwgY29uZi5pbnQgPSBUUlVFLCBjb25mLmxldmVsID0gMC45NSkNCiAgdGVtcF91dGVzdFtpLCAyOjVdIDwtIGModGVtcF93aGl0bmV5JHAudmFsdWUsIHRlbXBfd2hpdG5leSRlc3RpbWF0ZSwgdGVtcF93aGl0bmV5JGNvbmYuaW50KQ0KfQ0KDQojIER1bXAgZGVzIGRvbm7DqWVzIGRlcyB0ZXN0cyBVIMOgIGRldXggYm9ybmVzIGRlIE1hbm4tV2hpdG5leQ0KZndyaXRlKGFjY3VyYWN5LCAic3RhdHMvdV90ZXN0LmNzdiIpDQoNCiMgVGFibGVhdSBpbnRlcmFjdGlmIGR1IFUgdGVzdCBkZSBNYW5uLVdoaXRuZXkNCmRhdGF0YWJsZSh0ZW1wX3V0ZXN0LA0KICAgICAgICBmaWx0ZXIgPSAidG9wIiwgIyBGaWx0cmFnZSBhdS1kZXNzdXMgZGUgbGEgdGFibGUNCiAgICAgICAgY2xhc3MgPSAiY2VsbC1ib3JkZXIgc3RyaXBlIiwgIyBDU1MNCiAgICAgICAgZXh0ZW5zaW9ucyA9IGMoIkNvbFJlb3JkZXIiLA0KICAgICAgICAgICAgICAgICAgICAgICAiUm93UmVvcmRlciIpLCAjIFJlb3Jkb25uZXIgbWFudWVsbGVtZW50IMOgIGxhIG1haW4NCiAgICAgICAgb3B0aW9ucyA9IGxpc3Qob3JkZXIgPSBsaXN0KGxpc3QoMiwgImRlc2MiKSksICMgT3Jkb25uZXIgcGFyIGTDqWZhdXQgcGFyIGxlcyBmYWN0ZXVycyBheWFudCBsYSBwLnZhbHVlIGxhIHBsdXMgZ3JhbmRlDQogICAgICAgICAgICAgICAgICAgICAgIGNvbFJlb3JkZXIgPSBUUlVFLCAjIFBsdWdpbg0KICAgICAgICAgICAgICAgICAgICAgICByb3dSZW9yZGVyID0gVFJVRSkpICU+JSAjIFBsdWdpbg0KICBmb3JtYXRTdHlsZSgicF92YWx1ZSIsDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihjKDAsIDEpLCAnbGlnaHRibHVlJyksICMgQ291bGV1ciBibGV1ZSBwb3VyIGxlIGNvZWZmaWNpZW50DQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kU2l6ZSA9ICcxMDAlIDkwJScsDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kUG9zaXRpb24gPSAnY2VudGVyJykgJT4lDQogIGZvcm1hdFN0eWxlKGMoIm1lZGlhbl9lc3QiLCAibWVkaWFuX2luZjk1IiwgIm1lZGlhbl9zdXA5NSIpLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihyYW5nZSh0ZW1wX3V0ZXN0WywgMzo1XSksICdwaW5rJyksICMgQ291bGV1ciByb3NlIHBvdXIgbGEgZGlmZsOpcmVuY2UgZGUgbcOpZGlhbmUgZXN0aW3DqWUNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFNpemUgPSAnMTAwJSA5MCUnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgIGJhY2tncm91bmRQb3NpdGlvbiA9ICdjZW50ZXInKSAlPiUNCiAgZm9ybWF0Um91bmQoY29sdW1ucyA9IGMoInBfdmFsdWUiLCAibWVkaWFuX2VzdCIsICJtZWRpYW5faW5mOTUiLCAibWVkaWFuX3N1cDk1IiksDQogICAgICAgICAgICAgIGRpZ2l0cyA9IDYpDQoNCiMgQ3LDqWF0aW9uIGRlIGxhIHRhYmxlIHBvdXIgbGEgdmlzdWFsaXNhdGlvbg0KdGVtcF9sbSA8LSBjb3B5KG1pbmlfbG0pDQpjb2xuYW1lcyh0ZW1wX2xtKSA8LSBjKHBhc3RlMChyZXAoYyhwYXN0ZTAoIkNvZWYiLCAxOjQpLCBwYXN0ZTAoIlLDqXNpIiwgMTo0KSksIDQpLCBwYXN0ZTAoIl8iLCBpbnZlcnNlLnJsZShsaXN0KGxlbmd0aHMgPSByZXAoOCwgNCksIHZhbHVlcyA9IDE6NCkpKSksIHBhc3RlMCgiUG9zSW5pdGlhbGVfIiwgMTo0KSkNCg0KdGltaW5nKEN1cnJlbnRUaW1lLCAiQ2FsY3VscyBzdGF0aXN0aXF1ZXMiKQ0KDQojIFRhYmxlcGxvdCBkZXMgY29lZmZpY2llbnRzLCB0cmFqZWN0b2lyZXMsIGV0IHNhbGxlcw0KcGxvdCh0YWJsZXBsb3QoZGF0ID0gY2JpbmQoVHJhamVjdG9pcmUgPSBhcy5mYWN0b3IoZ3JvdXBfcGF0aCRwYXRoX0lEKSwgU2FsbGUgPSBhcy5mYWN0b3IoZ3JvdXBfcm9vbSRkYXRhc2V0X0lEKSwgdGVtcF9sbVssIGMoMTo0LCA5OjEyLCAxNzoyMCwgMjU6MjgpXSksIHNvcnRDb2wgPSAyLCBuQmlucyA9IDIwLCBzY2FsZXMgPSAibGluIiwgcGxvdCA9IEZBTFNFKSwgdGl0bGUgPSAiVHJhamVjdG9pcmUgdnMgQ29lZmZpY2llbnRzIikNCg0KIyBUYWJsZXBsb3QgZGVzIHLDqXNpZHVzLCB0cmFqZWN0b2lyZXMsIGV0IHNhbGxlcw0KcGxvdCh0YWJsZXBsb3QoZGF0ID0gY2JpbmQoVHJhamVjdG9pcmUgPSBhcy5mYWN0b3IoZ3JvdXBfcGF0aCRwYXRoX0lEKSwgU2FsbGUgPSBhcy5mYWN0b3IoZ3JvdXBfcm9vbSRkYXRhc2V0X0lEKSwgdGVtcF9sbVssIGMoNTo4LCAxMzoxNiwgMjE6MjQsIDI5OjMyKV0pLCBzb3J0Q29sID0gMiwgbkJpbnMgPSAyMCwgc2NhbGVzID0gImxpbiIsIHBsb3QgPSBGQUxTRSksIHRpdGxlID0gIlRyYWplY3RvaXJlIHZzIFLDqXNpZHVzIikNCg0KIyBUYWJsZXBsb3QgZGVzIHBvc2l0aW9ucyBpbml0aWFsZXMsIHRyYWplY3RvaXJlcywgZXQgc2FsbGVzDQpwbG90KHRhYmxlcGxvdChkYXQgPSBjYmluZChUcmFqZWN0b2lyZSA9IGFzLmZhY3Rvcihncm91cF9wYXRoJHBhdGhfSUQpLCBTYWxsZSA9IGFzLmZhY3Rvcihncm91cF9yb29tJGRhdGFzZXRfSUQpLCB0ZW1wX2xtWywgYygzMzozNildKSwgc29ydENvbCA9IDIsIG5CaW5zID0gMjAsIHNjYWxlcyA9ICJsaW4iLCBwbG90ID0gRkFMU0UpLCB0aXRsZSA9ICJUcmFqZWN0b2lyZSB2cyBQb3NpdGlvbiBJbml0aWFsZSIpDQpgYGANCg0KIyMgQW5hbHlzZSBkdSBtb2TDqGxlIGRlIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUNCg0KUG91ciBhbmFseXNlciBsZSBtb2TDqGxlIGRlIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUsIG5vdXMgYWxsb25zIHV0aWxpc2VyIGxlIG1vZMOobGUgbGluw6lhaXJlIHhnYm9vc3QgcXVpIGVzdCB1biBtb2TDqGxlIHRyw6hzIHNpbXBsZSAow6lxdWl2YWxlbnQgZCd1bmUgKiJyw6lncmVzc2lvbiBsaW7DqWFpcmUgYXZlYyB1bmUgYXBwbGljYXRpb24gc2lnbW/Dr2RhbGUgKFNvZnRtYXgpIiopLiBJbCByZXN0ZSBjbGFpciBxdWUgbGEgc2FsbGUgMSBkb25uZSB0b3Vqb3VycyBkZXMgcHJvYmzDqG1lcywgbWFpcyBsZXMgcsOpc3VsdGF0cyBzb250IGTDqWrDoCBiaWVuIG1laWxsZXVycyBhdmVjIHVuZSBleGFjdGl0dWRlIGQnZW52aXJvbiA1NyUhIChxdWkgZXN0IG5ldHRlbWVudCBzdXDDqXJpZXVyZSBhdSBtb2TDqGxlIGFsw6lhdG9pcmUsIGRldmFudCBhdHRlaW5kcmUgdW5pcXVlbWVudCBgciBzcHJpbnRmKCIlMDUuMDJmIiwgMTAwICogbWF4KHRhYnVsYXRlKGdyb3VwX3BhdGgkcGF0aF9JRFtncm91cF9yb29tJGRhdGFzZXRfSUQgPT0gMV0pKSAvIHN1bSh0YWJ1bGF0ZShncm91cF9wYXRoJHBhdGhfSURbZ3JvdXBfcm9vbSRkYXRhc2V0X0lEID09IDFdKSkpYCUgZGUgcHLDqWNpc2lvbikuDQoNClBvdXIgw6l2aXRlciB0b3V0ZSBjb25mdXNpb24sIG5vdXMgdXRpbGlzZXJvbnMgbGEgc8OpbWFudGlxdWUgc3VpdmFudGUgOiBGb2xkID0gU2FsbGUuIERlIHBsdXMsIG5vdXMgdXRpbGlzZXJvbnMgbGVzIHZhbGV1cnMgYWJzb2x1ZXMgYWZpbiBkZSBuZSBwYXMgaW1wYWN0ZXIgbGVzIG5vbWJyZXMgdmVycyB6w6lyb3MgKHMnaWxzIHBhcmFpc3NlbnQgw6AgbGEgZm9pcyBuw6lnYXRpZnMgZXQgcG9zaXRpZnMsIHBvdXIgZGlmZsOpcmVudHMgZm9sZHMpLiBMZXMgc2lnbmVzIHNvbnQgZG9ubsOpcyBzw6lwYXLDqW1lbnQuDQoNCkxhIG1hdHJpY2UgZGUgY29uZnVzaW9uIG5vdXMgbW9udHJlIGNsYWlyZWVtbnQgcXVlIGxlcyBjaGVtaW5zIDEgZXQgMiBzb250IHByb2Jsw6ltYXRpcXVlcyA6IGlscyBzZSBtw6lsYW5nZW50LiBEZSBtw6ptZSBwb3VyIGxhIHNhbGxlIDMsIGRvbnQgbCdleGFjdGl0dWRlIGRlIGxhIHByw6lkaWN0aW9uIG4nZXN0IHF1ZSBkZSAxMiUuDQoNCmBgYHtyIEFuYWx5c2VMb2dpc3RpcXVlMSwgd2FybmluZ3MgPSBGQUxTRX0NCiMgQ29tcHRldXIgZGUgdGVtcHMNCkN1cnJlbnRUaW1lIDwtIHRpbWVyKCkgIyBDaHVuayBQcsOpcGFyYXRpb24gZGUgbCdhbmFseXNlIGR1IG1vZMOobGUgZGUgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZQ0KDQojIFByw6ktaW5pdGlhbGlzYXRpb24gZGVzIHZhcmlhYmxlcw0KcHJlZGljdGVkVmFsdWVzIDwtIG1hdHJpeChucm93ID0gMzE0LCBuY29sID0gNikNCmV2b2x1dGlvbiA8LSBsaXN0KCkNCnRlbXBfZHQgPC0gbGlzdCgpDQp0ZW1wX21lYW5zIDwtIGRhdGEuZnJhbWUoRmVhdHVyZSA9IGMocGFzdGUwKHJlcChjKHBhc3RlMCgiQ29lZiIsIDE6NCksIHBhc3RlMCgiUsOpc2kiLCAxOjQpKSwgNCksIHBhc3RlMCgiXyIsIGludmVyc2UucmxlKGxpc3QobGVuZ3RocyA9IHJlcCg4LCA0KSwgdmFsdWVzID0gMTo0KSkpKSwgcGFzdGUwKCJQb3NJbml0aWFsZV8iLCAxOjQpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGb2xkXzEgPSBudW1lcmljKDM2KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGb2xkXzIgPSBudW1lcmljKDM2KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGb2xkXzMgPSBudW1lcmljKDM2KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGb2xkX01lYW4gPSBudW1lcmljKDM2KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGZWF0dXJlX01lYW4gPSBudW1lcmljKDM2KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGZWF0dXJlX1NEID0gbnVtZXJpYygzNikpDQoNCiMgQm91Y2xlIGQnZW50cmFpbmVtZW50IDIgY29udHJlIDENCmZvciAoaSBpbiAxMDoxMikgew0KICANCiAgIyBFbnRyYWluZW1lbnQgZCd1biBtb2TDqGxlIGxpbsOpYWlyZQ0KICB0ZW1wX21vZGVsIDwtIHhnYi50cmFpbihkYXRhID0gdHJhaW5pbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgIG51bV9jbGFzcyA9IDYsICMgQ2xhc3NpZmljYXRpb24gw6AgNiBjbGFzc2VzDQogICAgICAgICAgICAgICAgICAgICAgICAgIG50aHJlYWQgPSAxLCAjIDEgY29ldXIgdXRpbGlzw6kNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdW5kcyA9IDEwMDAwMDAsICMgTm9tYnJlIGQnaXTDqXJhdGlvbnMgZGUgYm9vc3RpbmcNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZXRhID0gMC4xMCwgIyBTaHJpbmthZ2UgcG91ciBsZSBib29zdGluZw0KICAgICAgICAgICAgICAgICAgICAgICAgICBib29zdGVyID0gImdibGluZWFyIiwgIyBUeXBlIGQnZW50cmFpbmVtZW50IDogbGluw6lhaXJlIG91IG5vbi1saW7DqWFpcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gIm11bHRpOnNvZnRwcm9iIiwgIyBHcmFkaWVudC9IZXNzaWFuIHBvdXIgbCdvcHRpbWlzYXRpb24gcGFyIEdyYWRpZW50IERlc2NlbnQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZXZhbF9tZXRyaWMgPSAibWVycm9yIiwgIyBJbmV4YWN0aXR1ZGUgZGUgbGEgY2xhc3NpZmljYXRpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4aW1pemUgPSBGQUxTRSwgIyBNaW5pbWlzYXRpb24gZGUgbCdlcnJldXINCiAgICAgICAgICAgICAgICAgICAgICAgICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gMTAwLCAjIEFycsOqdCBhcHLDqHMgMTAwIGl0w6lyYXRpb25zIHNhbnMgYW3DqWxpb3JhdGlvbiBkZSBsYSBtw6l0cmlxdWUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZSA9IEZBTFNFLCAjIFNhbnMgcHJpbnQgZGVzIGl0w6lyYXRpb25zDQogICAgICAgICAgICAgICAgICAgICAgICAgIHdhdGNobGlzdCA9IGxpc3QodGVzdCA9IHRlc3RpbmdfeGdiW1tpXV0pLCAjIEVzdGltYXRpb24gc3VyIGxlcyBkb25uw6llcyBkZSB0ZXN0DQogICAgICAgICAgICAgICAgICAgICAgICAgIGNhbGxiYWNrcyA9IGxpc3QoY2IuZXZhbHVhdGlvbi5sb2coKSkpICMgTG9nZ2luZyBkZXMgZG9ubsOpZXMgZCdlbnRyYWluZW1lbnQgcG91ciBwb3V2b2lyIHLDqWN1cMOpcmVyIGxlcyBtw6l0cmlxdWVzDQogIA0KICAjIEVucmVnaXN0cmVtZW50IGR1IGxvZw0KICBldm9sdXRpb25bW2kgLSA5XV0gPC0gY2JpbmQodGVtcF9tb2RlbCRldmFsdWF0aW9uX2xvZywgRm9sZCA9IHJlcCgxMyAtIGksIHRlbXBfbW9kZWwkbml0ZXIpKQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbWVpbGxldXIgbW9kw6hsZSAob2J0ZW50aW9uIGRlcyBtZWlsbGV1cnMgY29lZmZpY2llbnRzKQ0KICB0ZW1wX21vZGVsIDwtIHhnYi50cmFpbihkYXRhID0gdHJhaW5pbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgIG51bV9jbGFzcyA9IDYsICMgQ2xhc3NpZmljYXRpb24gw6AgNiBjbGFzc2VzDQogICAgICAgICAgICAgICAgICAgICAgICAgIG50aHJlYWQgPSAxLCAjIDEgY29ldXIgdXRpbGlzw6kNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdW5kcyA9IHRlbXBfbW9kZWwkYmVzdF9pdGVyYXRpb24sICMgTm9tYnJlIGQnaXTDqXJhdGlvbnMgZGUgYm9vc3RpbmcNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZXRhID0gMC4xMCwgIyBTaHJpbmthZ2UgcG91ciBsZSBib29zdGluZw0KICAgICAgICAgICAgICAgICAgICAgICAgICBib29zdGVyID0gImdibGluZWFyIiwgIyBUeXBlIGQnZW50cmFpbmVtZW50IDogbGluw6lhaXJlIG91IG5vbi1saW7DqWFpcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gIm11bHRpOnNvZnRwcm9iIiwgIyBHcmFkaWVudC9IZXNzaWFuIHBvdXIgbCdvcHRpbWlzYXRpb24gcGFyIEdyYWRpZW50IERlc2NlbnQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZXZhbF9tZXRyaWMgPSAibWVycm9yIiwgIyBJbmV4YWN0aXR1ZGUgZGUgbGEgY2xhc3NpZmljYXRpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4aW1pemUgPSBGQUxTRSwgIyBNaW5pbWlzYXRpb24gZGUgbCdlcnJldXINCiAgICAgICAgICAgICAgICAgICAgICAgICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gOTk5OTksICMgU2FucyBhcnLDqnQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZSA9IEZBTFNFLCAjIFNhbnMgcHJpbnQgZGVzIGl0w6lyYXRpb25zDQogICAgICAgICAgICAgICAgICAgICAgICAgIHdhdGNobGlzdCA9IGxpc3QodGVzdCA9IHRlc3RpbmdfeGdiW1tpXV0pLCAjIEVzdGltYXRpb24gc3VyIGxlcyBkb25uw6llcyBkZSB0ZXN0DQogICAgICAgICAgICAgICAgICAgICAgICAgIGNhbGxiYWNrcyA9IGxpc3QoY2IuZXZhbHVhdGlvbi5sb2coKSkpICMgTG9nZ2luZyBkZXMgZG9ubsOpZXMgZCdlbnRyYWluZW1lbnQgcG91ciBwb3V2b2lyIHLDqWN1cMOpcmVyIGxlcyBtw6l0cmlxdWVzDQogIA0KICAjIFByw6lkaWN0aW9uIGR1IG1vZMOobGUgbGluw6lhaXJlDQogIHByZWRpY3RlZFZhbHVlc1tmb2xkc190ZXN0W1tpXV0sIF0gPC0gdChtYXRyaXgocHJlZGljdCh0ZW1wX21vZGVsLCB0ZXN0aW5nX3hnYltbaV1dLCBudHJlZWxpbWl0ID0gMCksIG5yb3cgPSA2KSkNCiAgDQogICMgQ2FsY3VsIGV0IGZvcm1hdHRhZ2UgZGUgbCdpbXBvcnRhbmNlIGRlcyB2YXJpYWJsZXMNCiAgdGVtcF9pbXBvcnRhbmNlIDwtIGRhdGEudGFibGUoRmVhdHVyZSA9IHRlbXBfbWVhbnNbWyJGZWF0dXJlIl1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXRyaXgoeGdiLmltcG9ydGFuY2UobW9kZWwgPSB0ZW1wX21vZGVsKSRXZWlnaHQsIG5jb2wgPSA2KSkNCiAgY29sbmFtZXModGVtcF9pbXBvcnRhbmNlKSA8LSBjKCJGZWF0dXJlIiwgcGFzdGUwKCJMYWJlbF8iLCAxOjYpKQ0KICB0ZW1wX2ltcG9ydGFuY2VbWyJTaWduIl1dIDwtIHBhc3RlMChpZmVsc2UodGVtcF9pbXBvcnRhbmNlW1syXV0gPj0gMCwgIisiLCAiLSIpLCBpZmVsc2UodGVtcF9pbXBvcnRhbmNlW1szXV0gPj0gMCwgIisiLCAiLSIpLCBpZmVsc2UodGVtcF9pbXBvcnRhbmNlW1s0XV0gPj0gMCwgIisiLCAiLSIpLCBpZmVsc2UodGVtcF9pbXBvcnRhbmNlW1s1XV0gPj0gMCwgIisiLCAiLSIpLCBpZmVsc2UodGVtcF9pbXBvcnRhbmNlW1s2XV0gPj0gMCwgIisiLCAiLSIpLCBpZmVsc2UodGVtcF9pbXBvcnRhbmNlW1s3XV0gPj0gMCwgIisiLCAiLSIpKQ0KICB0ZW1wX2ltcG9ydGFuY2VbLCAyOjddIDwtIGFicyh0ZW1wX2ltcG9ydGFuY2VbLCAyOjcsIHdpdGggPSBGQUxTRV0pIA0KICB0ZW1wX21lYW5zW1sxNCAtIGldXSA8LSByb3dNZWFucyh0ZW1wX2ltcG9ydGFuY2VbLCAyOjcsIHdpdGggPSBGQUxTRV0pDQogIHRlbXBfaW1wb3J0YW5jZVtbcGFzdGUwKCJGb2xkXyIsIDEzIC0gaSwgIl9NZWFuIildXSA8LSB0ZW1wX21lYW5zW1sxNCAtIGldXQ0KICANCiAgIyBFbnJlZ2lzdHJlbWVudCBzb3VzIGZvcm1lIGRlIHRhYmxlYXUgaW50ZXJhY3RpZg0KICB0ZW1wX2R0W1tpIC0gOV1dIDwtIGRhdGF0YWJsZSh0ZW1wX2ltcG9ydGFuY2UsDQogICAgICAgIGZpbHRlciA9ICJ0b3AiLCAjIEZpbHRyYWdlIGF1LWRlc3N1cyBkZSBsYSB0YWJsZQ0KICAgICAgICBjbGFzcyA9ICJjZWxsLWJvcmRlciBzdHJpcGUiLCAjIENTUw0KICAgICAgICBleHRlbnNpb25zID0gYygiQ29sUmVvcmRlciIsDQogICAgICAgICAgICAgICAgICAgICAgICJSb3dSZW9yZGVyIiksICMgUmVvcmRvbm5lciBtYW51ZWxsZW1lbnQgw6AgbGEgbWFpbg0KICAgICAgICBvcHRpb25zID0gbGlzdChvcmRlciA9IGxpc3QobGlzdCg5LCAiZGVzYyIpKSwgIyBPcmRvbm5lciBwYXIgZMOpZmF1dCBwYXIgbGVzIGZhY3RldXJzIGF5YW50IGxlIHBvaWRzIGxlIHBsdXMgZ3Jvcw0KICAgICAgICAgICAgICAgICAgICAgICBjb2xSZW9yZGVyID0gVFJVRSwgIyBQbHVnaW4NCiAgICAgICAgICAgICAgICAgICAgICAgcm93UmVvcmRlciA9IFRSVUUpKSAlPiUgIyBQbHVnaW4NCiAgZm9ybWF0U3R5bGUocGFzdGUwKCJMYWJlbF8iLCAxOjYpLA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IHN0eWxlQ29sb3JCYXIocmFuZ2UodGVtcF9pbXBvcnRhbmNlWywgMjo3LCB3aXRoID0gRkFMU0VdKSwgJ2xpZ2h0Ymx1ZScpLCAjIENvdWxldXIgYmxldWUgcG91ciBsZSBjb2VmZmljaWVudA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZFNpemUgPSAnMTAwJSA5MCUnLA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZFJlcGVhdCA9ICduby1yZXBlYXQnLA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZFBvc2l0aW9uID0gJ2NlbnRlcicpICU+JQ0KICBmb3JtYXRTdHlsZShwYXN0ZTAoIkZvbGRfIiwgMTMgLSBpLCAiX01lYW4iKSwNCiAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IHN0eWxlQ29sb3JCYXIocmFuZ2UodGVtcF9pbXBvcnRhbmNlW1s5XV0pLCAncGluaycpLCAjIENvdWxldXIgcm9zZSBwb3VyIGxhIG3DqXRyaXF1ZSBkZSBtb3llbm5lDQogICAgICAgICAgICAgIGJhY2tncm91bmRTaXplID0gJzEwMCUgOTAlJywNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFJlcGVhdCA9ICduby1yZXBlYXQnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUG9zaXRpb24gPSAnY2VudGVyJykgJT4lDQogIGZvcm1hdFJvdW5kKGNvbHVtbnMgPSBjKHBhc3RlMCgiTGFiZWxfIiwgMTo2KSwgcGFzdGUwKCJGb2xkXyIsIDEzIC0gaSwgIl9NZWFuIikpLA0KICAgICAgICAgICAgICBkaWdpdHMgPSA2KQ0KICANCn0NCg0KIyBDYWxjdWwgZHUgcG9pZHMgbW95ZW4gYWZmZWN0w6kgw6AgY2hhcXVlIGZlYXR1cmUNCnRlbXBfbWVhbnNbWzVdXSA8LSByb3dNZWFucyh0ZW1wX21lYW5zWywgMjo0XSkgIyBQb2lkcyBtb3llbg0KdGVtcF9tZWFuc1tbNl1dIDwtIGFwcGx5KG1pbmlfbG0sIDIsIGZ1bmN0aW9uKHgpIHttZWFuKHgpfSkgIyBNb3llbm5lIGRlIGxhIGZlYXR1cmUgZGFucyBsZXMgZG9ubsOpZXMNCnRlbXBfbWVhbnNbWzddXSA8LSBhcHBseShtaW5pX2xtLCAyLCBmdW5jdGlvbih4KSB7c2QoeCl9KSAjIEVjYXJ0LXR5cGUgZGUgbGEgZmVhdHVyZSBkYW5zIGxlcyBkb25uw6llcw0KDQojIFByw6lwcmF0aW9uIGR1IHRhYmxlYXUgaW50ZXJhY3RpZiBzdXIgbGVzIHBvaWRzIG1veWVucyBhZ3LDqWfDqXMNCnRlbXBfZHRbWzRdXSA8LSBkYXRhdGFibGUodGVtcF9tZWFucywNCiAgICAgICAgZmlsdGVyID0gInRvcCIsICMgRmlsdHJhZ2UgYXUtZGVzc3VzIGRlIGxhIHRhYmxlDQogICAgICAgIGNsYXNzID0gImNlbGwtYm9yZGVyIHN0cmlwZSIsICMgQ1NTDQogICAgICAgIGV4dGVuc2lvbnMgPSBjKCJDb2xSZW9yZGVyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgIlJvd1Jlb3JkZXIiKSwgIyBSZW9yZG9ubmVyIG1hbnVlbGxlbWVudCDDoCBsYSBtYWluDQogICAgICAgIG9wdGlvbnMgPSBsaXN0KG9yZGVyID0gbGlzdChsaXN0KDUsICJkZXNjIikpLCAjIE9yZG9ubmVyIHBhciBkw6lmYXV0IHBhciBsZXMgZmFjdGV1cnMgYXlhbnQgbGUgcG9pZHMgbGUgcGx1cyBncm9zIGVuIG1veWVubmUNCiAgICAgICAgICAgICAgICAgICAgICAgY29sUmVvcmRlciA9IFRSVUUsICMgUGx1Z2luDQogICAgICAgICAgICAgICAgICAgICAgIHJvd1Jlb3JkZXIgPSBUUlVFKSkgJT4lICMgUGx1Z2luDQogIGZvcm1hdFN0eWxlKHBhc3RlMCgiRm9sZF8iLCAxOjMpLA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IHN0eWxlQ29sb3JCYXIocmFuZ2UodGVtcF9tZWFuc1ssIDI6NF0pLCAnbGlnaHRibHVlJyksICMgQ291bGV1ciBibGV1ZSBwb3VyIGxlIGNvZWZmaWNpZW50DQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kU2l6ZSA9ICcxMDAlIDkwJScsDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kUG9zaXRpb24gPSAnY2VudGVyJykgJT4lDQogIGZvcm1hdFN0eWxlKCJGb2xkX01lYW4iLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihyYW5nZSh0ZW1wX21lYW5zW1s1XV0pLCAncGluaycpLCAjIENvdWxldXIgcm9zZSBwb3VyIGxhIG3DqXRyaXF1ZSBkZSBtb3llbm5lDQogICAgICAgICAgICAgIGJhY2tncm91bmRTaXplID0gJzEwMCUgOTAlJywNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFJlcGVhdCA9ICduby1yZXBlYXQnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUG9zaXRpb24gPSAnY2VudGVyJykgJT4lDQogIGZvcm1hdFN0eWxlKCJGZWF0dXJlX01lYW4iLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihyYW5nZSh0ZW1wX21lYW5zW1s2XV0pLCAnbGlnaHRncmVlbicpLCAjIENvdWxldXIgdmVydGUgcG91ciBsYSBtb3llbm5lIGRlcyBmZWF0dXJlcw0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kU2l6ZSA9ICcxMDAlIDkwJScsDQogICAgICAgICAgICAgIGJhY2tncm91bmRSZXBlYXQgPSAnbm8tcmVwZWF0JywNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFBvc2l0aW9uID0gJ2NlbnRlcicpICU+JQ0KICBmb3JtYXRTdHlsZSgiRmVhdHVyZV9TRCIsDQogICAgICAgICAgICAgIGJhY2tncm91bmQgPSBzdHlsZUNvbG9yQmFyKHJhbmdlKHRlbXBfbWVhbnNbWzddXSksICdvcmFuZ2UnKSwgIyBDb3VsZXVyIHZlcnRlIHBvdXIgbCfDqWNhcnQtdHlwZSBkZXMgZmVhdHVyZXMNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFNpemUgPSAnMTAwJSA5MCUnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgIGJhY2tncm91bmRQb3NpdGlvbiA9ICdjZW50ZXInKSAlPiUNCiAgZm9ybWF0Um91bmQoY29sdW1ucyA9IGMocGFzdGUwKCJGb2xkXyIsIDE6MyksICJGb2xkX01lYW4iLCAiRmVhdHVyZV9NZWFuIiwgIkZlYXR1cmVfU0QiKSwNCiAgICAgICAgICAgICAgZGlnaXRzID0gNikNCg0KIyBEw6lwaXZvdGFnZSBkdSBsb2cNCmV2b2x1dGlvbiA8LSByYmluZGxpc3QoZXZvbHV0aW9uKQ0KY29sbmFtZXMoZXZvbHV0aW9uKSA8LSBjKCJJdGVyYXRpb24iLCAiRXhhY3RpdHVkZSIsICJGb2xkIikNCmV2b2x1dGlvbiRFeGFjdGl0dWRlIDwtIDEgLSBldm9sdXRpb24kRXhhY3RpdHVkZQ0KZXZvbHV0aW9uJEZvbGQgPC0gYXMuZmFjdG9yKGV2b2x1dGlvbiRGb2xkKQ0KDQojIFByw6lkaWN0aW9uIMOgIHBhcnRpciBkZXMgcHJvYmFiaWxpdMOpcw0KcHJlZGljdGVkTGFiZWwgPC0gZGF0YS5mcmFtZShMYWJlbCA9IGdyb3VwX3BhdGgkcGF0aF9JRCwgUHJlZGljdGlvbiA9IGFwcGx5KHByZWRpY3RlZFZhbHVlcywgMSwgZnVuY3Rpb24oeCkge3doaWNoLm1heCh4KX0pKQ0KDQp0aW1pbmcoQ3VycmVudFRpbWUsICJQcsOpcGFyYXRpb24gZGUgbCdhbmFseXNlIGR1IG1vZMOobGUgZGUgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSIpDQoNCiMgQWZmaWNoYWdlIGRlIGwnw6l2b2x1dGlvbiBkZSBsYSBwZXJmb3JtYW5jZSBkdSBtb2TDqGxlIHNlbG9uIGxlIG5vbWJyZSBkJ2l0w6lyYXRpb24sIHNvdXMgZm9ybWUgZGUgcGxvdCBpbnRlcmFjdGlmDQpnZ3Bsb3RseShnZ3Bsb3QoZGF0YSA9IGV2b2x1dGlvbiwgYWVzX3N0cmluZyh4ID0gIkl0ZXJhdGlvbiIsIHkgPSAiRXhhY3RpdHVkZSIsIGdyb3VwID0gIkZvbGQiLCBjb2xvciA9ICJGb2xkIikpICsgZ2VvbV9saW5lKCkgKyBnZW9tX3BvaW50KCkgKyBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQyIikgKyB0aGVtZV9idygpICsgbGFicyh0aXRsZSA9ICJFdm9sdXRpb24gZGUgbCdleGFjdGl0dWRlIHBhciByYXBwb3J0IGF1IG5vbWJyZSBkJ2l0w6lyYXRpb25zIGQnZW50cmFpbmVtZW50IiksIHdpZHRoID0gOTYwLCBoZWlnaHQgPSA3MjApDQoNCiMgQWZmaWNoYWdlIGRlIGxhIG1hdHJpY2UgZGUgY29uZnVzaW9uIHNvdXMgZm9ybWUgZGUgcGxvdCBpbnRlcmFjdGlmDQpjb25mdXNpb25fbWF0IDwtIGV4cGFuZC5ncmlkKExhYmVsID0gMTo2LCBQcmVkaWN0aW9uID0gMTo2KQ0KY29uZnVzaW9uX21hdCA8LSBtZXJnZShjb25mdXNpb25fbWF0LCBkYXRhLnRhYmxlKHByZWRpY3RlZExhYmVsKVssIGxpc3QoRnJlcSA9IHN1bSguTikpLCBieSA9IGxpc3QoTGFiZWwsIFByZWRpY3Rpb24pXSwgYnkgPSBjKCJMYWJlbCIsICJQcmVkaWN0aW9uIiksIGFsbC54ID0gVFJVRSkNCmNvbmZ1c2lvbl9tYXRbWyJGcmVxIl1dW2lzLm5hKGNvbmZ1c2lvbl9tYXRbWyJGcmVxIl1dKV0gPC0gMA0KZ2dwbG90bHkoZ2dwbG90KCkgKyBnZW9tX3JlY3QoZGF0YSA9IGRhdGEuZnJhbWUoY2VudCA9IDE6NiksIHNpemUgPSAyLCBmaWxsID0gTkEsIGNvbG91ciA9ICJibGFjayIsIGFlcyh4bWluID0gY2VudCAtIDAuNSwgeG1heCA9IGNlbnQgKyAwLjUsIHltaW4gPSBjZW50IC0gMC41LCB5bWF4ID0gY2VudCArIDAuNSkpICsgZ2VvbV90aWxlKGRhdGEgPSBjb25mdXNpb25fbWF0LCBhZXNfc3RyaW5nKHggPSAiTGFiZWwiLCB5ID0gIlByZWRpY3Rpb24iLCBmaWxsID0gIkZyZXEiKSkgKyBnZW9tX3RleHQoZGF0YSA9IGNvbmZ1c2lvbl9tYXQsIGFlc19zdHJpbmcoeCA9ICJMYWJlbCIsIHkgPSAiUHJlZGljdGlvbiIsIGxhYmVsID0gIkZyZXEiKSkgKyBzY2FsZV94X2Rpc2NyZXRlKG5hbWUgPSAiVHJhamVjdG9pcmUgUsOpZWxsZSIpICsgc2NhbGVfeV9kaXNjcmV0ZShuYW1lID0gIlRyYWplY3RvaXJlIFByw6lkaXRlIikgKyBzY2FsZV9maWxsX2dyYWRpZW50bihjb2xvdXJzID0gcmV2KGJyZXdlci5wYWxfZXh0ZW5kZWQoMywgIlBpWUciKSkpICsgbGFicyh0aXRsZSA9ICJNYXRyaWNlIGRlIENvbmZ1c2lvbiBkZSBsYSBUcmFqZWN0b2lyZSIsIGZpbGwgPSAiRnLDqXF1ZW5jZSIpLCB3aWR0aCA9IDk2MCwgaGVpZ2h0ID0gNzIwKQ0KDQojIEFmZmljaGFnZSBkZXMgdGFibGVzIMOgIGxhIGZpbiBjYXIgbGUgZm9ybWF0dGFnZSBwb3Nzw6hkZSB1biBidWcgaW5ow6lyZW50IGxvcnNxdSdvbiBhIHBsdXNpZXVycyBkYXRhdGFibGVzIChEVCkgZGFucyBsZSBtw6ptZSBjaHVuaw0KIyBodG1sdG9vbHM6OnRhZ0xpc3QodGVtcF9kdFtbM11dLCB0ZW1wX2R0W1syXV0sIHRlbXBfZHRbWzFdXSwgdGVtcF9kdFtbNF1dKQ0KYGBgDQoNCmBgYHtyIFRlbXAxLCBlY2hvPUZBTFNFfQ0KdGVtcF9kdFtbM11dICMgQ29lZmZpY2llbnRzIGNvbnRyZSBsYSBzYWxsZSAzDQpgYGANCg0KYGBge3IgVGVtcDIsIGVjaG89RkFMU0V9DQp0ZW1wX2R0W1syXV0gIyBDb2VmZmljaWVudHMgY29udHJlIGxhIHNhbGxlIDINCmBgYA0KDQpgYGB7ciBUZW1wMywgZWNobz1GQUxTRX0NCnRlbXBfZHRbWzFdXSAjIENvZWZmaWNpZW50cyBjb250cmUgbGEgc2FsbGUgMQ0KYGBgDQoNCmBgYHtyIFRlbXA0LCBlY2hvPUZBTFNFfQ0KdGVtcF9kdFtbNF1dICMgQ29lZmZpY2llbnRzIGFncsOpZ8Opcw0KYGBgDQoNCiMgVHJvaXNpw6htZSBBbmFseXNlIFN5c3TDqW1pcXVlDQoNCk5vdHJlIGRlcm5pw6hyZSBhbmFseXNlIHN5c3TDqW1pcXVlIHBvcnRlIHN1ciBsJ3V0aWxpc2F0aW9uIGRlcyBhbmNyZXMgcXVpIHNvbnQgY2Vuc8OpZXMgw6p0cmUgY29ycmVjdGVzIDogaWwgZmF1dCBpbnZlcnNlciBsZXMgYW5jcmVzIDEgZXQgMywgZXQgMiBldCA0IGRlIGxhIHNhbGxlIDEuDQoNCiMjIENvcnJlY3Rpb24gZmluYWxlIGRlcyBhbmNyZXMNCg0KUG91ciBjb3JyaWdlciBsZXMgYW5jcmVzLCBpbCBzdWZmaXQgZGUgcsOpYWxpc2VyIGNldHRlIG9ww6lyYXRpb24gc3VyIGxhIHNhbGxlIDEgOg0KDQotIEFuY3JlIDEgPT4gQW5jcmUgMw0KLSBBbmNyZSAyID0+IEFuY3JlIDQNCi0gQW5jcmUgMyA9PiBBbmNyZSAxDQotIEFuY3JlIDQgPT4gQW5jcmUgMg0KDQpgYGB7ciBDb3JyZWN0aW9uRmluYWxlfQ0KIyBDb21wdGV1ciBkZSB0ZW1wcw0KQ3VycmVudFRpbWUgPC0gdGltZXIoKSAjIENodW5rIENvcnJlY3Rpb24gZGVzIGFuY3Jlcw0KDQojIFJlY29waWUgZGVzIGZlYXR1cmVzIGRhbnMgbGUgc2VucyBjb3JyZWN0IChhbmNyZSAxPD0+MywgYW5jcmUgMjw9PjQpDQpmb3IgKGkgaW4gd2hpY2goZ3JvdXBfcm9vbSRkYXRhc2V0X0lEID09IDEpKSB7DQogIGRhdGFfcHJlW1tpXV1bWzFdXSA8LSBkYXRhX3ByZV9vbGRbW2ldXVtbM11dDQogIGRhdGFfcHJlW1tpXV1bWzNdXSA8LSBkYXRhX3ByZV9vbGRbW2ldXVtbMV1dDQogIGRhdGFfcHJlW1tpXV1bWzJdXSA8LSBkYXRhX3ByZV9vbGRbW2ldXVtbNF1dDQogIGRhdGFfcHJlW1tpXV1bWzRdXSA8LSBkYXRhX3ByZV9vbGRbW2ldXVtbMl1dDQp9DQoNCiMgRW5yZWdpc3RyZW1lbnQgZGVzIGZyYW1lcyBjb3JyaWfDqWVzIGV0IGRlcyBhbmNpZW5uZXMgZnJhbWVzLCBwb3VyIGF2b2lyIHVuIHNldCBzb2xpZGUsIGVuIHJlc3BlY3RhbnQgbGEgbm9tZW5jbGF0dXJlIGluaXRpYWxlIE1vdmVtZW50QUFMX1JTU194eHguY3N2Lg0KZm9yIChpIGluIDE6MzE0KSB7DQogIGZ3cml0ZShkYXRhX3ByZVtbaV1dLCBwYXN0ZTAoImRhdGFzZXRfY29ycmVjdGVkL01vdmVtZW50QUFMX1JTU18iLCBpLCAiLmNzdiIpKQ0KfQ0KDQojIFRlbXBzIG7DqWNlc3NhaXJlDQp0aW1pbmcoQ3VycmVudFRpbWUsICJDb3JyZWN0aW9uIGRlcyBhbmNyZXMiKQ0KYGBgDQoNCiMjIENyw6lhdGlvbiBkZXMgZmVhdHVyZXMgY29ycmlnw6llcw0KDQpVbmUgZm9pcyBxdWUgbGVzIGFuY3JlcyBjb3JyZXNwb25kYW50IGF1eCBib25uZXMgYW5jcmVzIGRhbnMgbGEgc2FsbGUgMSwgb24gcGV1dCBjcsOpZXIgbGVzIGZlYXR1cmVzIGNvcnJpZ8OpZXMuDQoNCmBgYHtyIENyZWF0aW9uRmVhdHVyZXMzfQ0KIyBDb21wdGV1ciBkZSB0ZW1wcw0KQ3VycmVudFRpbWUgPC0gdGltZXIoKSAjIENodW5rIENyw6lhdGlvbiBkZXMgZmVhdHVyZXMgZmluYWxlcw0KDQojIFByw6ktaW5pdGlhbGlzYXRpb24gZGUgbGEgZnJhbWUNCm1pbmlfbG0gPC0gZGF0YS5mcmFtZShtYXRyaXgobnJvdyA9IDMxNCwgbmNvbCA9IDM2KSkNCg0KIyBCb3VjbGUgcGFyIHPDqXJpZSB0ZW1wb3JlbGxlDQpmb3IgKGkgaW4gMTozMTQpIHsNCiAgDQogICMgQm91Y2xlIHBhciBhbmNyZQ0KICBmb3IgKGogaW4gMTo0KSB7DQogICAgDQogICAgIyBFbnRyYWluZW1lbnQgZCd1biBtb2TDqGxlIGxpbsOpYWlyZSB1dGlsaXNhbnQgbGVzIGF1dHJlcyBhbmNyZXMsIGF2ZWMgbCdpbnRlcmNlcHRyaWNlDQogICAgdGVtcF9tb2RlbCA8LSBmYXN0TG1QdXJlKFggPSBjYmluZChhcy5tYXRyaXgoZGF0YV9wcmVbW2ldXVssICgxOjQpWy1qXSwgd2l0aCA9IEZBTFNFXSksIHJlcCgxLCBucm93KGRhdGFfcHJlW1tpXV0pKSksIHkgPSBkYXRhX3ByZVtbaV1dW1tqXV0pDQogICAgDQogICAgIyBFbnJlZ2lzdHJlbWVudCBkZXMgY29lZmZpY2llbnRzIGV0IGRlcyByw6lzaWR1cw0KICAgIG1pbmlfbG1baSwgKGogKiA4IC0gNyk6KGogKiA4KV0gPC0gYyh0ZW1wX21vZGVsJGNvZWZmaWNpZW50cywgdGVtcF9tb2RlbCRzdGRlcnIpDQogICAgDQogIH0NCiAgDQogICMgQWpvdXQgZHUgZGVybmllciDDqWzDqW1lbnQgZGUgbGEgc8OpcmllIHRlbXBvcmVsbGUgKDQgYW5jcmVzKQ0KICBtaW5pX2xtW2ksIDMzOjM2XSA8LSBkYXRhX3ByZVtbaV1dW25yb3coZGF0YV9wcmVbW2ldXSksIF0NCiAgDQp9DQoNCiMgRW5yZWdpc3RyZW1lbnQgZGVzIGRvbm7DqWVzIGF1IGZvcm1hdCBDU1YNCmZ3cml0ZShjYmluZChtaW5pX2xtLCBHcm91cCA9IGdyb3VwX3Jvb21bWyJkYXRhc2V0X0lEIl1dLCBMYWJlbCA9IGdyb3VwX3BhdGhbWyJwYXRoX0lEIl1dKSwgImZlYXR1cmVzL2ZlYXR1cmVzMy5jc3YiKQ0KDQojIFRlbXBzIG7DqWNlc3NhaXJlDQp0aW1pbmcoQ3VycmVudFRpbWUsICJDcsOpYXRpb24gZGVzIGZlYXR1cmVzIGZpbmFsZXMiKQ0KYGBgDQoNCiMjIEVucmVnaXN0cmVtZW50IGRlcyBmZWF0dXJlcw0KDQpVbmUgZm9pcyBjb3JyaWfDqWVzLCBvbiBlbnJlZ2lzdHJlIGxlcyBmZWF0dXJlcyBjb21tZSBvbiBhIGZhaXQgYXUgZMOpYnV0Lg0KDQpgYGB7ciBHZW5lcmF0aW9uRG9ubmVlczN9DQojIENvbXB0ZXVyIGRlIHRlbXBzDQpDdXJyZW50VGltZSA8LSB0aW1lcigpICMgUHLDqXBhcmF0aW9uIGRlIGwnw6l2YWx1YXRpb24gZHUgbW9kw6hsZSBmaW5hbA0KDQojIE/DuSBzYXV2ZWdhcmRlciBsZXMgZmljaGllcnMgPw0KZmlsZV90YWcgPC0gIjNfZGF0YS8iDQoNCiMgSW5pdGlhbGlzYXRpb24gZGUgbGEgdmFyaWFibGUgcXVpIGFjY3VlaWxsZXJhIGxhIHByw6ljaXNpb24NCmFjY3VyYWN5IDwtIGRhdGEuZnJhbWUobWF0cml4KG5yb3cgPSAxNiwgbmNvbCA9IDEzKSkNCmNvbG5hbWVzKGFjY3VyYWN5KSA8LSBjKCJGb2xkIiwgInhnYl9MaW5lYXJNb2RlbCIsICJ4Z2JfRGVjaXNpb25UcmVlIiwgInhnYl9SYW5kb21Gb3Jlc3QiLCAieGdiX0dyYWRpZW50Qm9vc3RpbmciLCAiaDJvX0xpbmVhck1vZGVsIiwgImgyb19EZWNpc2lvblRyZWUiLCAiaDJvX1JhbmRvbUZvcmVzdCIsICJoMm9fR3JhZGllbnRCb29zdGluZyIsICJoMm9fTk5fMzJ4Nl9SZUxVIiwgImgyb19OTl8zMng2X1NvZnQiLCAiaDJvX05OXzE2eDE2eDZfUmVMVSIsICJoMm9fTk5fMTZ4MTZ4Nl9Tb2Z0IikNCmFjY3VyYWN5WywgMV0gPC0gYygiRm9sZF8xdjIiLCAiRm9sZF8xdjMiLCAiRm9sZF8ydjEiLCAiRm9sZF8ydjMiLCAiRm9sZF8zdjEiLCAiRm9sZF8zdjIiLCAiRm9sZF8xdjIzIiwgIkZvbGRfMnYxMyIsICJGb2xkXzN2MTIiLCAiRm9sZF8xMnYzIiwgIkZvbGRfMTN2MiIsICJGb2xkXzIzdjEiLCAiTW95ZW5uZV8xYzEiLCAiTW95ZW5uZV8xYzIiLCAiTW95ZW5uZV8yYzEiLCAiTW95ZW5uZSIpDQoNCiMgSW5pdGlhbGlzYXRpb24gZGVzIGZvbGRzIHBvdXIgbGEgY3Jvc3MtdmFsaWRhdGlvbg0KZm9sZHNfdHJhaW4gPC0gbGlzdCgpDQpmb2xkc190ZXN0IDwtIGxpc3QoKQ0KdHJhaW5pbmdfZGF0YSA8LSBsaXN0KCkNCnRlc3RpbmdfZGF0YSA8LSBsaXN0KCkNCnRyYWluaW5nX3hnYiA8LSBsaXN0KCkNCnRlc3RpbmdfeGdiIDwtIGxpc3QoKQ0KdHJhaW5pbmdfaDJvIDwtIGxpc3QoKQ0KdGVzdGluZ19oMm8gPC0gbGlzdCgpDQpjb21iaW5hdGlvbnNfdHJhaW4gPC0gYyhsaXN0KDEsIDEsIDIsIDIsIDMsIDMpLCBjb21ibigzLCAxLCBzaW1wbGlmeSA9IEZBTFNFKSwgY29tYm4oMywgMiwgc2ltcGxpZnkgPSBGQUxTRSkpDQpjb21iaW5hdGlvbnNfdGVzdCA8LSBjKGxpc3QoMiwgMywgMSwgMywgMSwgMiksIHJldihjb21ibigzLCAyLCBzaW1wbGlmeSA9IEZBTFNFKSksIHJldihjb21ibigzLCAxLCBzaW1wbGlmeSA9IEZBTFNFKSkpDQp0ZW1wX2ZhY3RvcnMgPC0gYXMuZmFjdG9yKGdyb3VwX3BhdGgkcGF0aF9JRCkNCg0KIyBDcsOpYXRpb24gZGVzIGRvbm7DqWVzIGQnZW50cmFpbmVtZW50IGV0IGRlIHZhbGlkYXRpb24NCmZvciAoaSBpbiAxOjEyKSB7DQogIA0KICAjIENyw6lhdGlvbiBkZXMgZm9sZHMgZCdlbnRyYWluZW1lbnQgZXQgZGUgdmFsaWRhdGlvbg0KICBmb2xkc190cmFpbltbaV1dIDwtIHdoaWNoKGdyb3VwX3Jvb21bWyJkYXRhc2V0X0lEIl1dICVpbiUgY29tYmluYXRpb25zX3RyYWluW1tpXV0pDQogIGZvbGRzX3Rlc3RbW2ldXSA8LSB3aGljaChncm91cF9yb29tW1siZGF0YXNldF9JRCJdXSAlaW4lIGNvbWJpbmF0aW9uc190ZXN0W1tpXV0pDQogIA0KICAjIFJlY2hlcmNoZSBldCBzdXBwcmVzc2lvbiBkdSBsYWJlbCAzIGxvcnNxdWUgbGEgc2FsbGUgMSBlc3QgaXNvbMOpZSAoc29pdCBlbiB0cmFpbiBvbiBlbmzDqHZlIGVuIHRlc3QsIHNvaXQgZW4gdGVzdCBvbiBlbmzDqHZlIGVuIHRyYWluKQ0KICBpZiAoKGxlbmd0aChjb21iaW5hdGlvbnNfdHJhaW5bW2ldXSkgPT0gMSkgJiAoY29tYmluYXRpb25zX3RyYWluW1tpXV1bMV0gPT0gMSkpIHsNCiAgICBmb2xkc190ZXN0W1tpXV0gPC0gZm9sZHNfdGVzdFtbaV1dW2dyb3VwX3BhdGgkcGF0aF9JRFtmb2xkc190ZXN0W1tpXV1dICE9IDNdDQogIH0NCiAgaWYgKChsZW5ndGgoY29tYmluYXRpb25zX3Rlc3RbW2ldXSkgPT0gMSkgJiAoY29tYmluYXRpb25zX3Rlc3RbW2ldXVsxXSA9PSAxKSkgew0KICAgIGZvbGRzX3RyYWluW1tpXV0gPC0gZm9sZHNfdHJhaW5bW2ldXVtncm91cF9wYXRoJHBhdGhfSURbZm9sZHNfdHJhaW5bW2ldXV0gIT0gM10NCiAgfQ0KICANCiAgIyBDcsOpYXRpb24gZGVzIGRvbm7DqWVzIGQnZW50cmFpbmVtZW50IGV0IGRlIHZhbGlkYXRpb24NCiAgdHJhaW5pbmdfZGF0YVtbaV1dIDwtIG1pbmlfbG1bZm9sZHNfdHJhaW5bW2ldXSwgXQ0KICB0ZXN0aW5nX2RhdGFbW2ldXSA8LSBtaW5pX2xtW2ZvbGRzX3Rlc3RbW2ldXSwgXQ0KICANCiAgIyBFbnJlZ2lzdHJlbWVudCBkZXMgZG9ubsOpZXMgQ1NWDQogIGZ3cml0ZSh0cmFpbmluZ19kYXRhW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRyYWluTkxfIiwgc3ByaW50ZigiJTAyZCIsIGkpLCAiLmNzdiIpKQ0KICBmd3JpdGUodGVzdGluZ19kYXRhW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRlc3ROTF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuY3N2IikpDQogIA0KICAjIFRyYW5zZm9ybWF0aW9uIGRlcyBkb25uw6llcyBhdSBmb3JtYXQgYXBwcm9wcmnDqSBwb3VyIHhnYm9vc3QNCiAgdHJhaW5pbmdfeGdiW1tpXV0gPC0geGdiLkRNYXRyaXgoZGF0YSA9IGFzLm1hdHJpeCh0cmFpbmluZ19kYXRhW1tpXV0pLCBsYWJlbCA9IGdyb3VwX3BhdGgkcGF0aF9JRFtmb2xkc190cmFpbltbaV1dXSAtIDEpDQogIHRlc3RpbmdfeGdiW1tpXV0gPC0geGdiLkRNYXRyaXgoZGF0YSA9IGFzLm1hdHJpeCh0ZXN0aW5nX2RhdGFbW2ldXSksIGxhYmVsID0gZ3JvdXBfcGF0aCRwYXRoX0lEW2ZvbGRzX3Rlc3RbW2ldXV0gLSAxKQ0KICANCiAgIyBEdW1waW5nIGRlcyBkYXRhc2V0cyBiaW5haXJlcyB4Z2Jvb3N0DQogIHhnYi5ETWF0cml4LnNhdmUodHJhaW5pbmdfeGdiW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRyYWluTF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuZGF0YSIpKQ0KICB4Z2IuRE1hdHJpeC5zYXZlKHRlc3RpbmdfeGdiW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRlc3RMXyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5kYXRhIikpDQogIA0KICAjIFRyYW5zZm9ybWF0aW9uIGRlcyBkb25uw6llcyBhdSBmb3JtYXQgYXBwcm9wcmnDqSBwb3VyIEgyTw0KICB0cmFpbmluZ19oMm9bW2ldXSA8LSBhcy5oMm8oY2JpbmQoTGFiZWwgPSB0ZW1wX2ZhY3RvcnNbZm9sZHNfdHJhaW5bW2ldXV0sIHRyYWluaW5nX2RhdGFbW2ldXSkpDQogIHRlc3RpbmdfaDJvW1tpXV0gPC0gYXMuaDJvKGNiaW5kKExhYmVsID0gdGVtcF9mYWN0b3JzW2ZvbGRzX3Rlc3RbW2ldXV0sIHRlc3RpbmdfZGF0YVtbaV1dKSkNCiAgDQogICMgRW5yZWdpc3RyZW1lbnQgZGVzIGZyYW1lcyBIMk8gKENTViArIExhYmVsKQ0KICBoMm8uZXhwb3J0RmlsZSh0cmFpbmluZ19oMm9bW2ldXSwgcGFzdGUwKGZpbGVfdGFnLCAidHJhaW5MXyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5jc3YiKSwgZm9yY2UgPSBUUlVFKQ0KICBoMm8uZXhwb3J0RmlsZSh0ZXN0aW5nX2gyb1tbaV1dLCBwYXN0ZTAoZmlsZV90YWcsICJ0ZXN0TF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuY3N2IiksIGZvcmNlID0gVFJVRSkNCiAgDQp9DQoNCiMgVGVtcHMgbsOpY2Vzc2FpcmUNCnRpbWluZyhDdXJyZW50VGltZSwgIlByw6lwYXJhdGlvbiBkZSBsJ8OpdmFsdWF0aW9uIGR1IG1vZMOobGUgZmluYWwiKQ0KYGBgDQoNCiMjIEVudHJhaW5lbWVudCBkZXMgZG91emUgbW9kw6hsZXMNCg0KT24gcGV1dCBtYWludGVuYW50IHRlc3RlciBsZXMgZG91emUgbW9kw6hsZXMgZGUgbWFuacOocmUgZmlhYmxlLg0KDQpgYGB7ciBFbnRyYWluZW1lbnQzLCBjYWNoZT1UUlVFfQ0KIyBDb21wdGV1ciBkZSB0ZW1wcw0KQ3VycmVudFRpbWUgPC0gdGltZXIoKSAjIENodW5rIENyw6lhdGlvbiBldCDDqXZhbHVhdGlvbiBkZXMgZG91emUgbW9kw6hsZXMgZGUgYmVuY2htYXJrIGZpbmFsDQoNCiMgT8O5IHNhdXZlZ2FyZGVyIGxlcyBmaWNoaWVycyA/DQpmaWxlX3RhZyA8LSAiM19tb2RlbHMvIg0KZmlsZV9oMm8gPC0gIjNfbW9kZWxzIg0KDQojIEJvdWNsZSBkJ8OpdmFsdWF0aW9uDQpmb3IgKGkgaW4gMToxMikgew0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbW9kw6hsZSBkZSByw6lncmVzc2lvbiBsb2dpc3RpcXVlICh4Z2Jvb3N0KQ0KICB0ZW1wX21vZGVsIDwtIHhnYl9keW5hbWljX3RyYWluKHRyYWluID0gdHJhaW5pbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IHRlc3RpbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9vc3RlciA9ICJnYmxpbmVhciIsICMgTGluw6lhaXJlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdW5kcyA9IDEwMDAwMDAsICMgQXJyw6p0w6kgYXUgbWVpbGxldXIgcsOpc3VsdGF0DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtX3BhcmFsbGVsX3RyZWVzID0gMSkNCiAgeGdiLmR1bXAobW9kZWwgPSB0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgZm5hbWUgPSBwYXN0ZTAoZmlsZV90YWcsICJ4Z2JfZ2xtXyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5qc29uIiksICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICB3aXRoX3N0YXRzID0gVFJVRSwgIyBFbnJlZ2lzdHJlbWVudCBkZXMgc3RhdGlzdGlxdWVzIHNpIG1vZMOobGUgZ2J0cmVlDQogICAgICAgICAgIGR1bXBfZm9ybWF0ID0gImpzb24iKSAjIER1bXAgYXUgZm9ybWF0IGpzb24sIHLDqS11dGlsaXNhYmxlDQogIGFjY3VyYWN5W2ksIDJdIDwtIDEgLSB0ZW1wX21vZGVsJGV2YWx1YXRpb25fbG9nW1syXV1bdGVtcF9tb2RlbCRiZXN0X2l0ZXJhdGlvbl0gIyBSw6ljdXDDqXJhdGlvbiBkdSBtZWlsbGV1ciByw6lzdWx0YXQNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IG1vZMOobGUgZCdhcmJyZSBkZSBkw6ljaXNpb24gKHhnYm9vc3QpDQogIHRlbXBfbW9kZWwgPC0geGdiX2R5bmFtaWNfdHJhaW4odHJhaW4gPSB0cmFpbmluZ194Z2JbW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdGluZ194Z2JbW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib29zdGVyID0gImdidHJlZSIsICMgTm9uLWxpbsOpYWlyZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3VuZHMgPSAxLCAjIFVuIHNldWwgYXJicmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1fcGFyYWxsZWxfdHJlZXMgPSAxKQ0KICB4Z2IuZHVtcChtb2RlbCA9IHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICBmbmFtZSA9IHBhc3RlMChmaWxlX3RhZywgInhnYl9kdF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuanNvbiIpLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgd2l0aF9zdGF0cyA9IFRSVUUsICMgRW5yZWdpc3RyZW1lbnQgZGVzIHN0YXRpc3RpcXVlcyBzaSBtb2TDqGxlIGdidHJlZQ0KICAgICAgICAgICBkdW1wX2Zvcm1hdCA9ICJqc29uIikgIyBEdW1wIGF1IGZvcm1hdCBqc29uLCByw6ktdXRpbGlzYWJsZQ0KICBhY2N1cmFjeVtpLCAzXSA8LSAxIC0gdGVtcF9tb2RlbCRldmFsdWF0aW9uX2xvZ1tbMl1dWzFdICMgUsOpY3Vww6lyYXRpb24gZHUgbWVpbGxldXIgcsOpc3VsdGF0DQogIA0KICAjIEVudHJhaW5lbWVudCBkdSBtb2TDqGxlIGRlIFJhbmRvbSBGb3Jlc3QgKHhnYm9vc3QpDQogIHRlbXBfbW9kZWwgPC0geGdiX2R5bmFtaWNfdHJhaW4odHJhaW4gPSB0cmFpbmluZ194Z2JbW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdGluZ194Z2JbW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib29zdGVyID0gImdidHJlZSIsICMgTm9uLWxpbsOpYWlyZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3VuZHMgPSAxLCAjIFVuZSBzZXVsZSBpdMOpcmF0aW9uDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtX3BhcmFsbGVsX3RyZWVzID0gMjAwKSAjIERlIDIwMCBhcmJyZXMNCiAgeGdiLmR1bXAobW9kZWwgPSB0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgZm5hbWUgPSBwYXN0ZTAoZmlsZV90YWcsICJ4Z2JfcmZfIiwgc3ByaW50ZigiJTAyZCIsIGkpLCAiLmpzb24iKSwgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgIHdpdGhfc3RhdHMgPSBUUlVFLCAjIEVucmVnaXN0cmVtZW50IGRlcyBzdGF0aXN0aXF1ZXMgc2kgbW9kw6hsZSBnYnRyZWUNCiAgICAgICAgICAgZHVtcF9mb3JtYXQgPSAianNvbiIpICMgRHVtcCBhdSBmb3JtYXQganNvbiwgcsOpLXV0aWxpc2FibGUNCiAgYWNjdXJhY3lbaSwgNF0gPC0gMSAtIHRlbXBfbW9kZWwkZXZhbHVhdGlvbl9sb2dbWzJdXSAjIFLDqWN1cMOpcmF0aW9uIGR1IG1laWxsZXVyIHLDqXN1bHRhdA0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbW9kw6hsZSBkJ2FyYnJlIGRlIGTDqWNpc2lvbiBib29zdMOpIGF2ZWMgcHJvdGVjdGlvbiBjb250cmUgbCdvdmVyZml0dGluZyAoeGdib29zdCkNCiAgdGVtcF9tb2RlbCA8LSB4Z2JfZHluYW1pY190cmFpbih0cmFpbiA9IHRyYWluaW5nX3hnYltbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSB0ZXN0aW5nX3hnYltbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvb3N0ZXIgPSAiZ2J0cmVlIiwgIyBOb24tbGluw6lhaXJlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdW5kcyA9IDEwMDAwMDAsICMgQXJyw6p0w6kgYXUgbWVpbGxldXIgcsOpc3VsdGF0DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtX3BhcmFsbGVsX3RyZWVzID0gMSkNCiAgeGdiLmR1bXAobW9kZWwgPSB0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgZm5hbWUgPSBwYXN0ZTAoZmlsZV90YWcsICJ4Z2JfZ2J0XyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5qc29uIiksICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICB3aXRoX3N0YXRzID0gVFJVRSwgIyBFbnJlZ2lzdHJlbWVudCBkZXMgc3RhdGlzdGlxdWVzIHNpIG1vZMOobGUgZ2J0cmVlDQogICAgICAgICAgIGR1bXBfZm9ybWF0ID0gImpzb24iKSAjIER1bXAgYXUgZm9ybWF0IGpzb24sIHLDqS11dGlsaXNhYmxlDQogIGFjY3VyYWN5W2ksIDVdIDwtIDEgLSB0ZW1wX21vZGVsJGV2YWx1YXRpb25fbG9nW1syXV1bdGVtcF9tb2RlbCRiZXN0X2l0ZXJhdGlvbl0gIyBSw6ljdXDDqXJhdGlvbiBkdSBtZWlsbGV1ciByw6lzdWx0YXQNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IG1vZMOobGUgZGUgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSAoaDJvKQ0KICB0ZW1wX21vZGVsIDwtIGgyby5nbG0oeSA9IDEsDQogICAgICAgICAgICAgICAgICAgICAgICB0cmFpbmluZ19mcmFtZSA9IHRyYWluaW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgdmFsaWRhdGlvbl9mcmFtZSA9IHRlc3RpbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9pZCA9IHBhc3RlMCgiaDJvX2dsbV8iLCBzcHJpbnRmKCIlMDJkIiwgaSkpLCAjIE5vbSBkdSBtb2TDqGxlDQogICAgICAgICAgICAgICAgICAgICAgICBtYXhfaXRlcmF0aW9ucyA9IDEwMCwgIyAxMDAgaXTDqXJhdGlvbnMgZCdvcHRpbWlzYXRpb24NCiAgICAgICAgICAgICAgICAgICAgICAgIHNvbHZlciA9ICJJUkxTTSIsICMgU29sdmV1ciBwYXIgZMOpZmF1dA0KICAgICAgICAgICAgICAgICAgICAgICAgc3RhbmRhcmRpemUgPSBGQUxTRSwgIyBQYXMgZGUgc3RhbmRhcmRpc2F0aW9uIHB1aXNxdWUgWy0xLCAxXQ0KICAgICAgICAgICAgICAgICAgICAgICAgZmFtaWx5ID0gIm11bHRpbm9taWFsIiwgIyBDbGFzc2lmaWNhdGlvbiBtdWx0aS1jbGFzc2UNCiAgICAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSAwLCAjIFJlcHJvZHVjdGlvbiBkZXMgcsOpc3VsdGF0cw0KICAgICAgICAgICAgICAgICAgICAgICAgaW50ZXJjZXB0ID0gVFJVRSkNCiAgaDJvLmRvd25sb2FkX3Bvam8odGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgICAgICAgICAgIHBhdGggPSBmaWxlX2gybywgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgICAgICAgICAgIGdldF9qYXIgPSBGQUxTRSkgIyBQYXMgZGUgZmljaGllciAuamFyDQogIGFjY3VyYWN5W2ksIDZdIDwtIHRlbXBfbW9kZWxAbW9kZWwkdmFsaWRhdGlvbl9tZXRyaWNzQG1ldHJpY3MkaGl0X3JhdGlvX3RhYmxlWzEsIDJdDQogIA0KICAjIEVudHJhaW5lbWVudCBkdSBtb2TDqGxlIGQnYXJicmUgZGUgZMOpY2lzaW9uIChoMm8pDQogIHRlbXBfbW9kZWwgPC0gaDJvLnJhbmRvbUZvcmVzdCh5ID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluaW5nX2ZyYW1lID0gdHJhaW5pbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWxpZGF0aW9uX2ZyYW1lID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2lkID0gcGFzdGUwKCJoMm9fZHRfIiwgc3ByaW50ZigiJTAyZCIsIGkpKSwgIyBOb20gZHUgbW9kw6hsZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3JhdGUgPSAxLCAjIFRvdXRlcyBsZXMgb2JzZXJ2YXRpb25zIHNlcm9udCBwcmlzZXMgZW4gY29tcHRlIHBvdXIgbGUgc2V1bCBhcmJyZSBkZSBkw6ljaXNpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG10cmllcyA9IDM2LCAjIFRvdXRlcyBsZXMgZmVhdHVyZXMgc2Vyb250IHByaXNlcyBlbiBjb21wdGUgcG91ciBsZSBzZXVsIGFyYnJlIGRlIGTDqWNpc2lvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnRyZWVzID0gMSwgIyBVbiBzZXVsIGFyYnJlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWVkID0gMCkgIyBSZXByb2R1Y3Rpb24gZGVzIHLDqXN1bHRhdHMNCiAgaDJvLmRvd25sb2FkX3Bvam8odGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgICAgICAgICAgIHBhdGggPSBmaWxlX2gybywgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgICAgICAgICAgIGdldF9qYXIgPSBGQUxTRSkgIyBQYXMgZGUgZmljaGllciAuamFyDQogIGFjY3VyYWN5W2ksIDddIDwtIDEgLSBtaW4odGVtcF9tb2RlbEBtb2RlbCRzY29yaW5nX2hpc3RvcnkkdmFsaWRhdGlvbl9jbGFzc2lmaWNhdGlvbl9lcnJvciwgbmEucm0gPSBUUlVFKQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbW9kw6hsZSBkZSBSYW5kb20gRm9yZXN0IChoMm8pDQogIHRlbXBfbW9kZWwgPC0gaDJvLnJhbmRvbUZvcmVzdCh5ID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluaW5nX2ZyYW1lID0gdHJhaW5pbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWxpZGF0aW9uX2ZyYW1lID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2lkID0gcGFzdGUwKCJoMm9fcmZfIiwgc3ByaW50ZigiJTAyZCIsIGkpKSwgIyBOb20gZHUgbW9kw6hsZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3JhdGUgPSAwLjYzMiwgIyBCb290c3RyYXBwaW5nIC42MzIgcG91ciBjaGFxdWUgYXJicmUgZGUgZMOpY2lzaW9uDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJpZXMgPSAtMSwgIyBzcXJ0KDM2KSBmZWF0dXJlcyBzZXJvbnQgcHJpc2VzIGVuIGNvbXB0ZSBwb3VyIGNoYXF1ZSBhcmJyZSBkZSBkw6ljaXNpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlcyA9IDIwMCwgIyAyMDAgYXJicmVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWVkID0gMCkgIyBSZXByb2R1Y3Rpb24gZGVzIHLDqXN1bHRhdHMNCiAgaDJvLmRvd25sb2FkX3Bvam8odGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgICAgICAgICAgIHBhdGggPSBmaWxlX2gybywgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgICAgICAgICAgIGdldF9qYXIgPSBGQUxTRSkgIyBQYXMgZGUgZmljaGllciAuamFyDQogIGFjY3VyYWN5W2ksIDhdIDwtIDEgLSBtaW4odGVtcF9tb2RlbEBtb2RlbCRzY29yaW5nX2hpc3RvcnkkdmFsaWRhdGlvbl9jbGFzc2lmaWNhdGlvbl9lcnJvciwgbmEucm0gPSBUUlVFKQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbW9kw6hsZSBkJ2FyYnJlIGRlIGTDqWNpc2lvbiBib29zdMOpIGF2ZWMgcHJvdGVjdGlvbiBjb250cmUgbCdvdmVyZml0dGluZyAoaDJvKQ0KICB0ZW1wX21vZGVsIDwtIGgyby5nYm0oeSA9IDEsDQogICAgICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbmluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICB2YWxpZGF0aW9uX2ZyYW1lID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9pZCA9IHBhc3RlMCgiaDJvX2didF8iLCBzcHJpbnRmKCIlMDJkIiwgaSkpLCAjIE5vbSBkdSBtb2TDqGxlDQogICAgICAgICAgICAgICAgICAgICAgZGlzdHJpYnV0aW9uID0gIm11bHRpbm9taWFsIiwgIyBDbGFzc2lmaWNhdGlvbiBtdWx0aS1jbGFzc2UNCiAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVfcmF0ZSA9IDEsICMgUGFzIGRlIHByb2Nlc3N1cyBzdG9jaGFzdGlxdWUNCiAgICAgICAgICAgICAgICAgICAgICBudHJlZXMgPSAxMDAsICMgMTAwIGl0w6lyYXRpb25zIGRlIGJvb3N0aW5nIGF1IG1heGltdW0NCiAgICAgICAgICAgICAgICAgICAgICBzY29yZV9lYWNoX2l0ZXJhdGlvbiA9IFRSVUUsICMgTm90ZXIgbGEgdmFsZXVyIGRlIGNoYXF1ZSBpdMOpcmF0aW9uDQogICAgICAgICAgICAgICAgICAgICAgc3RvcHBpbmdfcm91bmRzID0gMTAsICMgQXJyw6p0IGFwcsOocyAxMCBpdMOpcmF0aW9ucyBzYW5zIGFtw6lsaW9yYXRvbiBkZSBsYSBtw6l0cmlxdWUNCiAgICAgICAgICAgICAgICAgICAgICBzdG9wcGluZ19tZXRyaWMgPSAibWlzY2xhc3NpZmljYXRpb24iLCAjIFN1cnZlaWxsZXIgbCdpbmV4YWN0aXR1ZGUgZGUgbGEgY2xhc3NpZmljYXRpb24gcG91ciBsJ2FycsOqdA0KICAgICAgICAgICAgICAgICAgICAgIHN0b3BwaW5nX3RvbGVyYW5jZSA9IDAuMDAwMDEsICMgQXJyw6p0ZXIgbG9yc3F1ZSBsYSBtw6l0cmlxdWUgc3RhZ25lIGRlIDAuMDAxJQ0KICAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSAwKSAjIFJlcHJvZHVjdGlvbiBkZXMgcsOpc3VsdGF0cw0KICBoMm8uZG93bmxvYWRfcG9qbyh0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgICAgICAgICAgcGF0aCA9IGZpbGVfaDJvLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgICAgICAgICAgZ2V0X2phciA9IEZBTFNFKSAjIFBhcyBkZSBmaWNoaWVyIC5qYXINCiAgYWNjdXJhY3lbaSwgOV0gPC0gMSAtIG1pbih0ZW1wX21vZGVsQG1vZGVsJHNjb3JpbmdfaGlzdG9yeSR2YWxpZGF0aW9uX2NsYXNzaWZpY2F0aW9uX2Vycm9yLCBuYS5ybSA9IFRSVUUpDQogIA0KICAjIEVudHJhaW5lbWVudCBkdSByw6lzZWF1IGRlIG5ldXJvbmVzIMOgIGFyY2hpdGVjdHVyZSAzMng2ICsgUmVMVSAoaDJvKQ0KICB0ZW1wX21vZGVsIDwtIGgyb19ubl90cmFpbih0cmFpbiA9IHRyYWluaW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgPSBwYXN0ZTAoImgyb19ubl8zMng2X1JlTFVfIiwgc3ByaW50ZigiJTAyZCIsIGkpKSwgIyBOb20gZHUgbW9kw6hsZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhY3RpdmF0aW9uID0gIlJlY3RpZmllciIsICMgUmVMVQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoaWRkZW4gPSAzMikgIyBBcmNoaXRlY3R1cmUgMzJ4Ng0KICBoMm8uZG93bmxvYWRfcG9qbyh0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgICAgICAgICAgcGF0aCA9IGZpbGVfaDJvLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgICAgICAgICAgZ2V0X2phciA9IEZBTFNFKSAjIFBhcyBkZSBmaWNoaWVyIC5qYXINCiAgYWNjdXJhY3lbaSwgMTBdIDwtIDEgLSBtaW4odGVtcF9tb2RlbEBtb2RlbCRzY29yaW5nX2hpc3RvcnkkdmFsaWRhdGlvbl9jbGFzc2lmaWNhdGlvbl9lcnJvciwgbmEucm0gPSBUUlVFKQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgcsOpc2VhdSBkZSBuZXVyb25lcyDDoCBhcmNoaXRlY3R1cmUgMzJ4NiArIFRhbmggKGgybykNCiAgdGVtcF9tb2RlbCA8LSBoMm9fbm5fdHJhaW4odHJhaW4gPSB0cmFpbmluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IHRlc3RpbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2lkID0gcGFzdGUwKCJoMm9fbm5fMzJ4Nl9UYW5oXyIsIHNwcmludGYoIiUwMmQiLCBpKSksICMgTm9tIGR1IG1vZMOobGUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWN0aXZhdGlvbiA9ICJUYW5oIiwgIyAiU2lnbW9pZGUiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhpZGRlbiA9IDMyKSAjIEFyY2hpdGVjdHVyZSAzMng2DQogIGgyby5kb3dubG9hZF9wb2pvKHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICAgICAgICAgICBwYXRoID0gZmlsZV9oMm8sICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICAgICAgICAgICBnZXRfamFyID0gRkFMU0UpICMgUGFzIGRlIGZpY2hpZXIgLmphcg0KICBhY2N1cmFjeVtpLCAxMV0gPC0gMSAtIG1pbih0ZW1wX21vZGVsQG1vZGVsJHNjb3JpbmdfaGlzdG9yeSR2YWxpZGF0aW9uX2NsYXNzaWZpY2F0aW9uX2Vycm9yLCBuYS5ybSA9IFRSVUUpDQogIA0KICAjIEVudHJhaW5lbWVudCBkdSByw6lzZWF1IGRlIG5ldXJvbmVzIMOgIGFyY2hpdGVjdHVyZSAxNngxNng2ICsgUmVMVSAoaDJvKQ0KICB0ZW1wX21vZGVsIDwtIGgyb19ubl90cmFpbih0cmFpbiA9IHRyYWluaW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgPSBwYXN0ZTAoImgyb19ubl8xNngxNng2X1JlTFVfIiwgc3ByaW50ZigiJTAyZCIsIGkpKSwgIyBOb20gZHUgbW9kw6hsZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhY3RpdmF0aW9uID0gIlJlY3RpZmllciIsICMgUmVMVQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoaWRkZW4gPSBjKDE2LCAxNikpICMgQXJjaGl0ZWN0dXJlIDE2eDE2eDYNCiAgaDJvLmRvd25sb2FkX3Bvam8odGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgICAgICAgICAgIHBhdGggPSBmaWxlX2gybywgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgICAgICAgICAgIGdldF9qYXIgPSBGQUxTRSkgIyBQYXMgZGUgZmljaGllciAuamFyDQogIGFjY3VyYWN5W2ksIDEyXSA8LSAxIC0gbWluKHRlbXBfbW9kZWxAbW9kZWwkc2NvcmluZ19oaXN0b3J5JHZhbGlkYXRpb25fY2xhc3NpZmljYXRpb25fZXJyb3IsIG5hLnJtID0gVFJVRSkNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IHLDqXNlYXUgZGUgbmV1cm9uZXMgw6AgYXJjaGl0ZWN0dXJlIDE2eDE2eDYgKyBUYW5oIChoMm8pDQogIHRlbXBfbW9kZWwgPC0gaDJvX25uX3RyYWluKHRyYWluID0gdHJhaW5pbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSB0ZXN0aW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9pZCA9IHBhc3RlMCgiaDJvX25uXzE2eDE2eDZfVGFuaF8iLCBzcHJpbnRmKCIlMDJkIiwgaSkpLCAjIE5vbSBkdSBtb2TDqGxlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFjdGl2YXRpb24gPSAiVGFuaCIsICMgIlNpZ21vaWRlIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoaWRkZW4gPSBjKDE2LCAxNikpICMgQXJjaGl0ZWN0dXJlIDE2eDE2eDYNCiAgaDJvLmRvd25sb2FkX3Bvam8odGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgICAgICAgICAgIHBhdGggPSBmaWxlX2gybywgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgICAgICAgICAgIGdldF9qYXIgPSBGQUxTRSkgIyBQYXMgZGUgZmljaGllciAuamFyDQogIGFjY3VyYWN5W2ksIDEzXSA8LSAxIC0gbWluKHRlbXBfbW9kZWxAbW9kZWwkc2NvcmluZ19oaXN0b3J5JHZhbGlkYXRpb25fY2xhc3NpZmljYXRpb25fZXJyb3IsIG5hLnJtID0gVFJVRSkNCiAgDQp9DQoNCiMgVGVtcHMgbsOpY2Vzc2FpcmUNCnRpbWluZyhDdXJyZW50VGltZSwgIkNyw6lhdGlvbiBldCDDqXZhbHVhdGlvbiBkZXMgZG91emUgbW9kw6hsZXMgZGUgYmVuY2htYXJrIGZpbmFsIikNCmBgYA0KDQojIyBBZmZpY2hhZ2UgZGVzIHLDqXN1bHRhdHMNCg0KTGVzIHLDqXN1bHRhdHMgZGUgbGEgcGVyZm9ybWFuY2UgZGVzIG1vZMOobGVzIGVudHJhaW7DqXMgc29udCBjaS1kZXNzb3VzLiBMZXMgcGVyZm9ybWFuY2VzIHNlbWJsZW50IGzDqWfDqHJlbWVudCBlbiBoYXVzc2UgKGRlIDIlIGVudmlyb24pLiBFbiByZXZhbmNoZSwgbGVzIHBlcmZvcm1hbmNlcyBzb250IHBsdXMgZmFpYmxlcyBzdXIgbG9yc3F1ZSBsZXMgZG9ubsOpZXMgc29udCB2YWxpZMOpZXMgc3VyIGxhIHNhbGxlIDEuDQoNCmBgYHtyIFJlc3VsdGF0czN9DQojIE1veWVubmUgZGVzIHLDqXN1bHRhdHMNCmZvciAoaSBpbiAyOjEzKSB7DQogIGFjY3VyYWN5WzEzLCBpXSA8LSBtZWFuKGFjY3VyYWN5WzE6NiwgaV0pDQogIGFjY3VyYWN5WzE0LCBpXSA8LSBtZWFuKGFjY3VyYWN5Wzc6OSwgaV0pDQogIGFjY3VyYWN5WzE1LCBpXSA8LSBtZWFuKGFjY3VyYWN5WzEwOjEyLCBpXSkNCiAgYWNjdXJhY3lbMTYsIGldIDwtIG1lYW4oYWNjdXJhY3lbMTM6MTUsIGldKQ0KfQ0KDQojIEVucmVnaXN0cmVtZW50IGRlcyBzY29yZXMNCmZ3cml0ZShhY2N1cmFjeSwgInNjb3Jlcy8zX21vZGVscy5jc3YiKQ0KDQojIEFmZmljaGFnZSBkZXMgcsOpc3VsdGF0cyBkYW5zIHVuIHRhYmxlYXUgaW50ZXJhY3RpZg0KdG9fcHJpbnQgPC0gZGF0YS50YWJsZSh0KGFjY3VyYWN5WzEzOjE2LCAtMV0pKSAjIFByw6lwYXJhdGlvbiBkZXMgZG9ubsOpZXMgw6AgbWV0dHJlIHN1ciB0YWJsZQ0KY29sbmFtZXModG9fcHJpbnQpIDwtIGMoIjEgY29udHJlIDEiLCAiMSBjb250cmUgMiIsICIyIGNvbnRyZSAxIiwgIk1veWVubmUiKSAjIFJlbWlzZSBkZXMgbm9tcyBkZXMgY29sb25uZXMNCnJvdy5uYW1lcyh0b19wcmludCkgPC0gY29sbmFtZXMoYWNjdXJhY3kpWy0xXSAjIFJlbWlzZSBkZXMgbm9tcyBkZXMgbGlnbmVzDQpkYXRhdGFibGUodG9fcHJpbnQsDQogICAgICAgICAgZmlsdGVyID0gInRvcCIsICMgRmlsdHJhZ2UgYXUtZGVzc3VzIGRlIGxhIHRhYmxlDQogICAgICAgICAgY2xhc3MgPSAiY2VsbC1ib3JkZXIgc3RyaXBlIiwgIyBDU1MNCiAgICAgICAgICBleHRlbnNpb25zID0gYygiQ29sUmVvcmRlciIsDQogICAgICAgICAgICAgICAgICAgICAgICAgIlJvd1Jlb3JkZXIiKSwgIyBSZW9yZG9ubmVyIG1hbnVlbGxlbWVudCDDoCBsYSBtYWluDQogICAgICAgICAgb3B0aW9ucyA9IGxpc3QocGFnZUxlbmd0aCA9IDEyLCAjIFBhZ2UgYWZmaWNoYW50IDEyIGxpZ25lcw0KICAgICAgICAgICAgICAgICAgICAgICAgIG9yZGVyID0gbGlzdChsaXN0KDQsICJkZXNjIikpLCAjIE9yZG9ubmVyIHBhciBkw6lmYXV0IHBhciBsJ2V4YWN0aXR1ZGUgbW95ZW5uZQ0KICAgICAgICAgICAgICAgICAgICAgICAgIGNvbFJlb3JkZXIgPSBUUlVFLCAjIFBsdWdpbg0KICAgICAgICAgICAgICAgICAgICAgICAgIHJvd1Jlb3JkZXIgPSBUUlVFKSkgJT4lICMgUGx1Z2luDQogIGZvcm1hdFN0eWxlKGMoIjEgY29udHJlIDEiLCAiMSBjb250cmUgMiIsICIyIGNvbnRyZSAxIiksDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihjKDAsIDEpLCAnbGlnaHRncmVlbicpLCAjIENvdWxldXIgdmVydCBjbGFpciBwb3VyIGxlcyBtw6l0cmlxdWVzIHBhciBmb2xkDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kU2l6ZSA9ICcxMDAlIDkwJScsDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kUG9zaXRpb24gPSAnY2VudGVyJykgJT4lDQogIGZvcm1hdFN0eWxlKCJNb3llbm5lIiwNCiAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IHN0eWxlQ29sb3JCYXIoYygwLCAxKSwgJ3BpbmsnKSwgIyBDb3VsZXVyIHJvc2UgcG91ciBsYSBtw6l0cmlxdWUgZGUgbW95ZW5uZQ0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kU2l6ZSA9ICcxMDAlIDkwJScsDQogICAgICAgICAgICAgIGJhY2tncm91bmRSZXBlYXQgPSAnbm8tcmVwZWF0JywNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFBvc2l0aW9uID0gJ2NlbnRlcicpICU+JQ0KICBmb3JtYXRQZXJjZW50YWdlKGNvbHVtbnMgPSBjKCIxIGNvbnRyZSAxIiwgIjEgY29udHJlIDIiLCAiMiBjb250cmUgMSIpLA0KICAgICAgICAgICAgICBkaWdpdHMgPSA4KSAlPiUNCiAgZm9ybWF0UGVyY2VudGFnZShjb2x1bW5zID0gIk1veWVubmUiLA0KICAgICAgICAgICAgICBkaWdpdHMgPSA4KQ0KDQojIEFmZmljaGFnZSBkZXMgcsOpc3VsdGF0cyBkYW5zIHVuIHRhYmxlYXUgc3RhdGlxdWUNCmZvcm1hdHRhYmxlKGFjY3VyYWN5WywgYygxLCAyOjUpXSwgbGlzdChmb3JtYXR0YWJsZTo6YXJlYShjb2wgPSB4Z2JfTGluZWFyTW9kZWw6eGdiX0dyYWRpZW50Qm9vc3RpbmcpIH4gY29sb3JfYmFyKCJvcmFuZ2UiKSkpDQpmb3JtYXR0YWJsZShhY2N1cmFjeVssIGMoMSwgNjo5KV0sIGxpc3QoZm9ybWF0dGFibGU6OmFyZWEoY29sID0gaDJvX0xpbmVhck1vZGVsOmgyb19HcmFkaWVudEJvb3N0aW5nKSB+IGNvbG9yX2JhcigiY3lhbiIpKSkNCmZvcm1hdHRhYmxlKGFjY3VyYWN5WywgYygxLCAxMDoxMyldLCBsaXN0KGZvcm1hdHRhYmxlOjphcmVhKGNvbCA9IGgyb19OTl8zMng2X1JlTFU6aDJvX05OXzE2eDE2eDZfU29mdCkgfiBjb2xvcl9iYXIoInllbGxvdyIpKSkNCmBgYA0KDQojIyBBbmFseXNlIGR1IG1vZMOobGUgZGUgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZQ0KDQpMZXMgY29lZmZpY2llbnRzIGFmZmVjdMOpcyBzZWxvbiBsZXMgw6ljaGFudGlsbG9ucyBkJ2VudHJhaW5lbWVudCBzZW1ibGVudCBtb2lucyBzdGFibGVzIHF1J2F1cGFyYXZhbnQsIHBldXQgw6p0cmUgdW4gcHJvYmzDqG1lIGQnw6ljaGVsbGUgKG91IGRlIFZJRiBlbnRyZSBsZXMgdmFyaWFibGVzKS4NCg0KT24gcmVtYXJxdWUgcXVlIGxlIGNoZW1pbiAyIGVzdCBiaWVuIG1pZXV4IGNsYXNzw6kgcXUnYXVwYXJhdmFudCwgbWFpcyBxdWUgbGEgc2FsbGUgMyBlc3QgdG91am91cnMgZGlmZmljaWxlIMOgIGNsYXNzZXIgKG9uIGVzdCBwYXNzw6kgZGUgMTIlIGQnZXhhY3RpdHVkZSDDoCAyNCUsIGNlIHF1aSBlc3QgZGV1eCBmb2lzIHBsdXMgcHLDqWNpcykuDQoNCkVuIHJldmFuY2hlLCBsYSBzYWxsZSA0IHNlbWJsZSBzb3VmZmlyICg0MCUgZCdleGFjdGl0dWRlKSwgY2UgcXVpIG4nw6l0YWlzIHBhcyBsZSBjYXMgYXZlYyBsJ2FwcHJveGltYXRpb24gZGVzIHNpZ25hdXggZGVzIGFuY3JlcyAoNTUlIGQnZXhhY3RpdHVkZSkuDQoNCmBgYHtyIEFuYWx5c2VMb2dpc3RpcXVlMiwgd2FybmluZ3MgPSBGQUxTRX0NCiMgQ29tcHRldXIgZGUgdGVtcHMNCkN1cnJlbnRUaW1lIDwtIHRpbWVyKCkgIyBDaHVuayBQcsOpcGFyYXRpb24gZGUgbCdhbmFseXNlIGR1IG1vZMOobGUgZGUgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSBmaW5hbA0KDQojIFByw6ktaW5pdGlhbGlzYXRpb24gZGVzIHZhcmlhYmxlcw0KcHJlZGljdGVkVmFsdWVzIDwtIG1hdHJpeChucm93ID0gMzE0LCBuY29sID0gNikNCmV2b2x1dGlvbiA8LSBsaXN0KCkNCnRlbXBfZHQgPC0gbGlzdCgpDQp0ZW1wX21lYW5zIDwtIGRhdGEuZnJhbWUoRmVhdHVyZSA9IGMocGFzdGUwKHJlcChjKHBhc3RlMCgiQ29lZiIsIDE6NCksIHBhc3RlMCgiUsOpc2kiLCAxOjQpKSwgNCksIHBhc3RlMCgiXyIsIGludmVyc2UucmxlKGxpc3QobGVuZ3RocyA9IHJlcCg4LCA0KSwgdmFsdWVzID0gMTo0KSkpKSwgcGFzdGUwKCJQb3NJbml0aWFsZV8iLCAxOjQpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGb2xkXzEgPSBudW1lcmljKDM2KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGb2xkXzIgPSBudW1lcmljKDM2KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGb2xkXzMgPSBudW1lcmljKDM2KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGb2xkX01lYW4gPSBudW1lcmljKDM2KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGZWF0dXJlX01lYW4gPSBudW1lcmljKDM2KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGZWF0dXJlX1NEID0gbnVtZXJpYygzNikpDQoNCiMgQm91Y2xlIGQnZW50cmFpbmVtZW50IDIgY29udHJlIDENCmZvciAoaSBpbiAxMDoxMikgew0KICANCiAgIyBFbnRyYWluZW1lbnQgZCd1biBtb2TDqGxlIGxpbsOpYWlyZQ0KICB0ZW1wX21vZGVsIDwtIHhnYi50cmFpbihkYXRhID0gdHJhaW5pbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgIG51bV9jbGFzcyA9IDYsICMgQ2xhc3NpZmljYXRpb24gw6AgNiBjbGFzc2VzDQogICAgICAgICAgICAgICAgICAgICAgICAgIG50aHJlYWQgPSAxLCAjIDEgY29ldXIgdXRpbGlzw6kNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdW5kcyA9IDEwMDAwMDAsICMgTm9tYnJlIGQnaXTDqXJhdGlvbnMgZGUgYm9vc3RpbmcNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZXRhID0gMC4xMCwgIyBTaHJpbmthZ2UgcG91ciBsZSBib29zdGluZw0KICAgICAgICAgICAgICAgICAgICAgICAgICBib29zdGVyID0gImdibGluZWFyIiwgIyBUeXBlIGQnZW50cmFpbmVtZW50IDogbGluw6lhaXJlIG91IG5vbi1saW7DqWFpcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gIm11bHRpOnNvZnRwcm9iIiwgIyBHcmFkaWVudC9IZXNzaWFuIHBvdXIgbCdvcHRpbWlzYXRpb24gcGFyIEdyYWRpZW50IERlc2NlbnQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZXZhbF9tZXRyaWMgPSAibWVycm9yIiwgIyBJbmV4YWN0aXR1ZGUgZGUgbGEgY2xhc3NpZmljYXRpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4aW1pemUgPSBGQUxTRSwgIyBNaW5pbWlzYXRpb24gZGUgbCdlcnJldXINCiAgICAgICAgICAgICAgICAgICAgICAgICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gMTAwLCAjIEFycsOqdCBhcHLDqHMgMTAwIGl0w6lyYXRpb25zIHNhbnMgYW3DqWxpb3JhdGlvbiBkZSBsYSBtw6l0cmlxdWUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZSA9IEZBTFNFLCAjIFNhbnMgcHJpbnQgZGVzIGl0w6lyYXRpb25zDQogICAgICAgICAgICAgICAgICAgICAgICAgIHdhdGNobGlzdCA9IGxpc3QodGVzdCA9IHRlc3RpbmdfeGdiW1tpXV0pLCAjIEVzdGltYXRpb24gc3VyIGxlcyBkb25uw6llcyBkZSB0ZXN0DQogICAgICAgICAgICAgICAgICAgICAgICAgIGNhbGxiYWNrcyA9IGxpc3QoY2IuZXZhbHVhdGlvbi5sb2coKSkpICMgTG9nZ2luZyBkZXMgZG9ubsOpZXMgZCdlbnRyYWluZW1lbnQgcG91ciBwb3V2b2lyIHLDqWN1cMOpcmVyIGxlcyBtw6l0cmlxdWVzDQogIA0KICAjIEVucmVnaXN0cmVtZW50IGR1IGxvZw0KICBldm9sdXRpb25bW2kgLSA5XV0gPC0gY2JpbmQodGVtcF9tb2RlbCRldmFsdWF0aW9uX2xvZywgRm9sZCA9IHJlcCgxMyAtIGksIHRlbXBfbW9kZWwkbml0ZXIpKQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbWVpbGxldXIgbW9kw6hsZSAob2J0ZW50aW9uIGRlcyBtZWlsbGV1cnMgY29lZmZpY2llbnRzKQ0KICB0ZW1wX21vZGVsIDwtIHhnYi50cmFpbihkYXRhID0gdHJhaW5pbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgIG51bV9jbGFzcyA9IDYsICMgQ2xhc3NpZmljYXRpb24gw6AgNiBjbGFzc2VzDQogICAgICAgICAgICAgICAgICAgICAgICAgIG50aHJlYWQgPSAxLCAjIDEgY29ldXIgdXRpbGlzw6kNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdW5kcyA9IHRlbXBfbW9kZWwkYmVzdF9pdGVyYXRpb24sICMgTm9tYnJlIGQnaXTDqXJhdGlvbnMgZGUgYm9vc3RpbmcNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZXRhID0gMC4xMCwgIyBTaHJpbmthZ2UgcG91ciBsZSBib29zdGluZw0KICAgICAgICAgICAgICAgICAgICAgICAgICBib29zdGVyID0gImdibGluZWFyIiwgIyBUeXBlIGQnZW50cmFpbmVtZW50IDogbGluw6lhaXJlIG91IG5vbi1saW7DqWFpcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gIm11bHRpOnNvZnRwcm9iIiwgIyBHcmFkaWVudC9IZXNzaWFuIHBvdXIgbCdvcHRpbWlzYXRpb24gcGFyIEdyYWRpZW50IERlc2NlbnQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZXZhbF9tZXRyaWMgPSAibWVycm9yIiwgIyBJbmV4YWN0aXR1ZGUgZGUgbGEgY2xhc3NpZmljYXRpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4aW1pemUgPSBGQUxTRSwgIyBNaW5pbWlzYXRpb24gZGUgbCdlcnJldXINCiAgICAgICAgICAgICAgICAgICAgICAgICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gOTk5OTksICMgU2FucyBhcnLDqnQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZSA9IEZBTFNFLCAjIFNhbnMgcHJpbnQgZGVzIGl0w6lyYXRpb25zDQogICAgICAgICAgICAgICAgICAgICAgICAgIHdhdGNobGlzdCA9IGxpc3QodGVzdCA9IHRlc3RpbmdfeGdiW1tpXV0pLCAjIEVzdGltYXRpb24gc3VyIGxlcyBkb25uw6llcyBkZSB0ZXN0DQogICAgICAgICAgICAgICAgICAgICAgICAgIGNhbGxiYWNrcyA9IGxpc3QoY2IuZXZhbHVhdGlvbi5sb2coKSkpICMgTG9nZ2luZyBkZXMgZG9ubsOpZXMgZCdlbnRyYWluZW1lbnQgcG91ciBwb3V2b2lyIHLDqWN1cMOpcmVyIGxlcyBtw6l0cmlxdWVzDQogIA0KICAjIFByw6lkaWN0aW9uIGR1IG1vZMOobGUgbGluw6lhaXJlDQogIHByZWRpY3RlZFZhbHVlc1tmb2xkc190ZXN0W1tpXV0sIF0gPC0gdChtYXRyaXgocHJlZGljdCh0ZW1wX21vZGVsLCB0ZXN0aW5nX3hnYltbaV1dLCBudHJlZWxpbWl0ID0gMCksIG5yb3cgPSA2KSkNCiAgDQogICMgQ2FsY3VsIGV0IGZvcm1hdHRhZ2UgZGUgbCdpbXBvcnRhbmNlIGRlcyB2YXJpYWJsZXMNCiAgdGVtcF9pbXBvcnRhbmNlIDwtIGRhdGEudGFibGUoRmVhdHVyZSA9IHRlbXBfbWVhbnNbWyJGZWF0dXJlIl1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXRyaXgoeGdiLmltcG9ydGFuY2UobW9kZWwgPSB0ZW1wX21vZGVsKSRXZWlnaHQsIG5jb2wgPSA2KSkNCiAgY29sbmFtZXModGVtcF9pbXBvcnRhbmNlKSA8LSBjKCJGZWF0dXJlIiwgcGFzdGUwKCJMYWJlbF8iLCAxOjYpKQ0KICB0ZW1wX2ltcG9ydGFuY2VbWyJTaWduIl1dIDwtIHBhc3RlMChpZmVsc2UodGVtcF9pbXBvcnRhbmNlW1syXV0gPj0gMCwgIisiLCAiLSIpLCBpZmVsc2UodGVtcF9pbXBvcnRhbmNlW1szXV0gPj0gMCwgIisiLCAiLSIpLCBpZmVsc2UodGVtcF9pbXBvcnRhbmNlW1s0XV0gPj0gMCwgIisiLCAiLSIpLCBpZmVsc2UodGVtcF9pbXBvcnRhbmNlW1s1XV0gPj0gMCwgIisiLCAiLSIpLCBpZmVsc2UodGVtcF9pbXBvcnRhbmNlW1s2XV0gPj0gMCwgIisiLCAiLSIpLCBpZmVsc2UodGVtcF9pbXBvcnRhbmNlW1s3XV0gPj0gMCwgIisiLCAiLSIpKQ0KICB0ZW1wX2ltcG9ydGFuY2VbLCAyOjddIDwtIGFicyh0ZW1wX2ltcG9ydGFuY2VbLCAyOjcsIHdpdGggPSBGQUxTRV0pDQogIHRlbXBfbWVhbnNbWzE0IC0gaV1dIDwtIHJvd01lYW5zKHRlbXBfaW1wb3J0YW5jZVssIDI6Nywgd2l0aCA9IEZBTFNFXSkNCiAgdGVtcF9pbXBvcnRhbmNlW1twYXN0ZTAoIkZvbGRfIiwgMTMgLSBpLCAiX01lYW4iKV1dIDwtIHRlbXBfbWVhbnNbWzE0IC0gaV1dDQogIA0KICAjIEVucmVnaXN0cmVtZW50IHNvdXMgZm9ybWUgZGUgdGFibGVhdSBpbnRlcmFjdGlmDQogIHRlbXBfZHRbW2kgLSA5XV0gPC0gZGF0YXRhYmxlKHRlbXBfaW1wb3J0YW5jZSwNCiAgICAgICAgZmlsdGVyID0gInRvcCIsICMgRmlsdHJhZ2UgYXUtZGVzc3VzIGRlIGxhIHRhYmxlDQogICAgICAgIGNsYXNzID0gImNlbGwtYm9yZGVyIHN0cmlwZSIsICMgQ1NTDQogICAgICAgIGV4dGVuc2lvbnMgPSBjKCJDb2xSZW9yZGVyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgIlJvd1Jlb3JkZXIiKSwgIyBSZW9yZG9ubmVyIG1hbnVlbGxlbWVudCDDoCBsYSBtYWluDQogICAgICAgIG9wdGlvbnMgPSBsaXN0KG9yZGVyID0gbGlzdChsaXN0KDksICJkZXNjIikpLCAjIE9yZG9ubmVyIHBhciBkw6lmYXV0IHBhciBsZXMgZmFjdGV1cnMgYXlhbnQgbGUgcG9pZHMgbGUgcGx1cyBncm9zDQogICAgICAgICAgICAgICAgICAgICAgIGNvbFJlb3JkZXIgPSBUUlVFLCAjIFBsdWdpbg0KICAgICAgICAgICAgICAgICAgICAgICByb3dSZW9yZGVyID0gVFJVRSkpICU+JSAjIFBsdWdpbg0KICBmb3JtYXRTdHlsZShwYXN0ZTAoIkxhYmVsXyIsIDE6NiksDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihyYW5nZSh0ZW1wX2ltcG9ydGFuY2VbLCAyOjcsIHdpdGggPSBGQUxTRV0pLCAnbGlnaHRibHVlJyksICMgQ291bGV1ciBibGV1ZSBwb3VyIGxlIGNvZWZmaWNpZW50DQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kU2l6ZSA9ICcxMDAlIDkwJScsDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kUG9zaXRpb24gPSAnY2VudGVyJykgJT4lDQogIGZvcm1hdFN0eWxlKHBhc3RlMCgiRm9sZF8iLCAxMyAtIGksICJfTWVhbiIpLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihyYW5nZSh0ZW1wX2ltcG9ydGFuY2VbWzldXSksICdwaW5rJyksICMgQ291bGV1ciByb3NlIHBvdXIgbGEgbcOpdHJpcXVlIGRlIG1veWVubmUNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFNpemUgPSAnMTAwJSA5MCUnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgIGJhY2tncm91bmRQb3NpdGlvbiA9ICdjZW50ZXInKSAlPiUNCiAgZm9ybWF0Um91bmQoY29sdW1ucyA9IGMocGFzdGUwKCJMYWJlbF8iLCAxOjYpLCBwYXN0ZTAoIkZvbGRfIiwgMTMgLSBpLCAiX01lYW4iKSksDQogICAgICAgICAgICAgIGRpZ2l0cyA9IDYpDQogIA0KfQ0KDQojIENhbGN1bCBkdSBwb2lkcyBtb3llbiBhZmZlY3TDqSDDoCBjaGFxdWUgZmVhdHVyZQ0KdGVtcF9tZWFuc1tbNV1dIDwtIHJvd01lYW5zKHRlbXBfbWVhbnNbLCAyOjRdKSAjIFBvaWRzIG1veWVuDQp0ZW1wX21lYW5zW1s2XV0gPC0gYXBwbHkobWluaV9sbSwgMiwgZnVuY3Rpb24oeCkge21lYW4oeCl9KSAjIE1veWVubmUgZGUgbGEgZmVhdHVyZSBkYW5zIGxlcyBkb25uw6llcw0KdGVtcF9tZWFuc1tbN11dIDwtIGFwcGx5KG1pbmlfbG0sIDIsIGZ1bmN0aW9uKHgpIHtzZCh4KX0pICMgRWNhcnQtdHlwZSBkZSBsYSBmZWF0dXJlIGRhbnMgbGVzIGRvbm7DqWVzDQoNCiMgUHLDqXByYXRpb24gZHUgdGFibGVhdSBpbnRlcmFjdGlmIHN1ciBsZXMgcG9pZHMgbW95ZW5zIGFncsOpZ8Opcw0KdGVtcF9kdFtbNF1dIDwtIGRhdGF0YWJsZSh0ZW1wX21lYW5zLA0KICAgICAgICBmaWx0ZXIgPSAidG9wIiwgIyBGaWx0cmFnZSBhdS1kZXNzdXMgZGUgbGEgdGFibGUNCiAgICAgICAgY2xhc3MgPSAiY2VsbC1ib3JkZXIgc3RyaXBlIiwgIyBDU1MNCiAgICAgICAgZXh0ZW5zaW9ucyA9IGMoIkNvbFJlb3JkZXIiLA0KICAgICAgICAgICAgICAgICAgICAgICAiUm93UmVvcmRlciIpLCAjIFJlb3Jkb25uZXIgbWFudWVsbGVtZW50IMOgIGxhIG1haW4NCiAgICAgICAgb3B0aW9ucyA9IGxpc3Qob3JkZXIgPSBsaXN0KGxpc3QoNSwgImRlc2MiKSksICMgT3Jkb25uZXIgcGFyIGTDqWZhdXQgcGFyIGxlcyBmYWN0ZXVycyBheWFudCBsZSBwb2lkcyBsZSBwbHVzIGdyb3MgZW4gbW95ZW5uZQ0KICAgICAgICAgICAgICAgICAgICAgICBjb2xSZW9yZGVyID0gVFJVRSwgIyBQbHVnaW4NCiAgICAgICAgICAgICAgICAgICAgICAgcm93UmVvcmRlciA9IFRSVUUpKSAlPiUgIyBQbHVnaW4NCiAgZm9ybWF0U3R5bGUocGFzdGUwKCJGb2xkXyIsIDE6MyksDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihyYW5nZSh0ZW1wX21lYW5zWywgMjo0XSksICdsaWdodGJsdWUnKSwgIyBDb3VsZXVyIGJsZXVlIHBvdXIgbGUgY29lZmZpY2llbnQNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRTaXplID0gJzEwMCUgOTAlJywNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRSZXBlYXQgPSAnbm8tcmVwZWF0JywNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRQb3NpdGlvbiA9ICdjZW50ZXInKSAlPiUNCiAgZm9ybWF0U3R5bGUoIkZvbGRfTWVhbiIsDQogICAgICAgICAgICAgIGJhY2tncm91bmQgPSBzdHlsZUNvbG9yQmFyKHJhbmdlKHRlbXBfbWVhbnNbWzVdXSksICdwaW5rJyksICMgQ291bGV1ciByb3NlIHBvdXIgbGEgbcOpdHJpcXVlIGRlIG1veWVubmUNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFNpemUgPSAnMTAwJSA5MCUnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgIGJhY2tncm91bmRQb3NpdGlvbiA9ICdjZW50ZXInKSAlPiUNCiAgZm9ybWF0U3R5bGUoIkZlYXR1cmVfTWVhbiIsDQogICAgICAgICAgICAgIGJhY2tncm91bmQgPSBzdHlsZUNvbG9yQmFyKHJhbmdlKHRlbXBfbWVhbnNbWzZdXSksICdsaWdodGdyZWVuJyksICMgQ291bGV1ciB2ZXJ0ZSBwb3VyIGxhIG1veWVubmUgZGVzIGZlYXR1cmVzDQogICAgICAgICAgICAgIGJhY2tncm91bmRTaXplID0gJzEwMCUgOTAlJywNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFJlcGVhdCA9ICduby1yZXBlYXQnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUG9zaXRpb24gPSAnY2VudGVyJykgJT4lDQogIGZvcm1hdFN0eWxlKCJGZWF0dXJlX1NEIiwNCiAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IHN0eWxlQ29sb3JCYXIocmFuZ2UodGVtcF9tZWFuc1tbN11dKSwgJ29yYW5nZScpLCAjIENvdWxldXIgdmVydGUgcG91ciBsJ8OpY2FydC10eXBlIGRlcyBmZWF0dXJlcw0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kU2l6ZSA9ICcxMDAlIDkwJScsDQogICAgICAgICAgICAgIGJhY2tncm91bmRSZXBlYXQgPSAnbm8tcmVwZWF0JywNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFBvc2l0aW9uID0gJ2NlbnRlcicpICU+JQ0KICBmb3JtYXRSb3VuZChjb2x1bW5zID0gYyhwYXN0ZTAoIkZvbGRfIiwgMTozKSwgIkZvbGRfTWVhbiIsICJGZWF0dXJlX01lYW4iLCAiRmVhdHVyZV9TRCIpLA0KICAgICAgICAgICAgICBkaWdpdHMgPSA2KQ0KDQojIETDqXBpdm90YWdlIGR1IGxvZw0KZXZvbHV0aW9uIDwtIHJiaW5kbGlzdChldm9sdXRpb24pDQpjb2xuYW1lcyhldm9sdXRpb24pIDwtIGMoIkl0ZXJhdGlvbiIsICJFeGFjdGl0dWRlIiwgIkZvbGQiKQ0KZXZvbHV0aW9uJEV4YWN0aXR1ZGUgPC0gMSAtIGV2b2x1dGlvbiRFeGFjdGl0dWRlDQpldm9sdXRpb24kRm9sZCA8LSBhcy5mYWN0b3IoZXZvbHV0aW9uJEZvbGQpDQoNCiMgUHLDqWRpY3Rpb24gw6AgcGFydGlyIGRlcyBwcm9iYWJpbGl0w6lzDQpwcmVkaWN0ZWRMYWJlbCA8LSBkYXRhLmZyYW1lKExhYmVsID0gZ3JvdXBfcGF0aCRwYXRoX0lELCBQcmVkaWN0aW9uID0gYXBwbHkocHJlZGljdGVkVmFsdWVzLCAxLCBmdW5jdGlvbih4KSB7d2hpY2gubWF4KHgpfSkpDQoNCnRpbWluZyhDdXJyZW50VGltZSwgIlByw6lwYXJhdGlvbiBkZSBsJ2FuYWx5c2UgZHUgbW9kw6hsZSBkZSByw6lncmVzc2lvbiBsb2dpc3RpcXVlIGZpbmFsIikNCg0KIyBBZmZpY2hhZ2UgZGUgbCfDqXZvbHV0aW9uIGRlIGxhIHBlcmZvcm1hbmNlIGR1IG1vZMOobGUgc2Vsb24gbGUgbm9tYnJlIGQnaXTDqXJhdGlvbiwgc291cyBmb3JtZSBkZSBwbG90IGludGVyYWN0aWYNCmdncGxvdGx5KGdncGxvdChkYXRhID0gZXZvbHV0aW9uLCBhZXNfc3RyaW5nKHggPSAiSXRlcmF0aW9uIiwgeSA9ICJFeGFjdGl0dWRlIiwgZ3JvdXAgPSAiRm9sZCIsIGNvbG9yID0gIkZvbGQiKSkgKyBnZW9tX2xpbmUoKSArIGdlb21fcG9pbnQoKSArIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlNldDIiKSArIHRoZW1lX2J3KCkgKyBsYWJzKHRpdGxlID0gIkV2b2x1dGlvbiBkZSBsJ2V4YWN0aXR1ZGUgcGFyIHJhcHBvcnQgYXUgbm9tYnJlIGQnaXTDqXJhdGlvbnMgZCdlbnRyYWluZW1lbnQiKSwgd2lkdGggPSA5NjAsIGhlaWdodCA9IDcyMCkNCg0KIyBBZmZpY2hhZ2UgZGUgbGEgbWF0cmljZSBkZSBjb25mdXNpb24gc291cyBmb3JtZSBkZSBwbG90IGludGVyYWN0aWYNCmNvbmZ1c2lvbl9tYXQgPC0gZXhwYW5kLmdyaWQoTGFiZWwgPSAxOjYsIFByZWRpY3Rpb24gPSAxOjYpDQpjb25mdXNpb25fbWF0IDwtIG1lcmdlKGNvbmZ1c2lvbl9tYXQsIGRhdGEudGFibGUocHJlZGljdGVkTGFiZWwpWywgbGlzdChGcmVxID0gc3VtKC5OKSksIGJ5ID0gbGlzdChMYWJlbCwgUHJlZGljdGlvbildLCBieSA9IGMoIkxhYmVsIiwgIlByZWRpY3Rpb24iKSwgYWxsLnggPSBUUlVFKQ0KY29uZnVzaW9uX21hdFtbIkZyZXEiXV1baXMubmEoY29uZnVzaW9uX21hdFtbIkZyZXEiXV0pXSA8LSAwDQpnZ3Bsb3RseShnZ3Bsb3QoKSArIGdlb21fcmVjdChkYXRhID0gZGF0YS5mcmFtZShjZW50ID0gMTo2KSwgc2l6ZSA9IDIsIGZpbGwgPSBOQSwgY29sb3VyID0gImJsYWNrIiwgYWVzKHhtaW4gPSBjZW50IC0gMC41LCB4bWF4ID0gY2VudCArIDAuNSwgeW1pbiA9IGNlbnQgLSAwLjUsIHltYXggPSBjZW50ICsgMC41KSkgKyBnZW9tX3RpbGUoZGF0YSA9IGNvbmZ1c2lvbl9tYXQsIGFlc19zdHJpbmcoeCA9ICJMYWJlbCIsIHkgPSAiUHJlZGljdGlvbiIsIGZpbGwgPSAiRnJlcSIpKSArIGdlb21fdGV4dChkYXRhID0gY29uZnVzaW9uX21hdCwgYWVzX3N0cmluZyh4ID0gIkxhYmVsIiwgeSA9ICJQcmVkaWN0aW9uIiwgbGFiZWwgPSAiRnJlcSIpKSArIHNjYWxlX3hfZGlzY3JldGUobmFtZSA9ICJUcmFqZWN0b2lyZSBSw6llbGxlIikgKyBzY2FsZV95X2Rpc2NyZXRlKG5hbWUgPSAiVHJhamVjdG9pcmUgUHLDqWRpdGUiKSArIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKGNvbG91cnMgPSByZXYoYnJld2VyLnBhbF9leHRlbmRlZCgzLCAiUGlZRyIpKSkgKyBsYWJzKHRpdGxlID0gIk1hdHJpY2UgZGUgQ29uZnVzaW9uIGRlIGxhIFRyYWplY3RvaXJlIiwgZmlsbCA9ICJGcsOpcXVlbmNlIiksIHdpZHRoID0gOTYwLCBoZWlnaHQgPSA3MjApDQoNCiMgQWZmaWNoYWdlIGRlcyB0YWJsZXMgw6AgbGEgZmluIGNhciBsZSBmb3JtYXR0YWdlIHBvc3PDqGRlIHVuIGJ1ZyBpbmjDqXJlbnQgbG9yc3F1J29uIGEgcGx1c2lldXJzIGRhdGF0YWJsZXMgKERUKSBkYW5zIGxlIG3Dqm1lIGNodW5rDQojIGh0bWx0b29sczo6dGFnTGlzdCh0ZW1wX2R0W1szXV0sIHRlbXBfZHRbWzJdXSwgdGVtcF9kdFtbMV1dLCB0ZW1wX2R0W1s0XV0pDQpgYGANCg0KYGBge3IgVGVtcDUsIGVjaG89RkFMU0V9DQp0ZW1wX2R0W1szXV0gIyBDb2VmZmljaWVudHMgY29udHJlIGxhIHNhbGxlIDMNCmBgYA0KDQpgYGB7ciBUZW1wNiwgZWNobz1GQUxTRX0NCnRlbXBfZHRbWzJdXSAjIENvZWZmaWNpZW50cyBjb250cmUgbGEgc2FsbGUgMg0KYGBgDQoNCmBgYHtyIFRlbXA3LCBlY2hvPUZBTFNFfQ0KdGVtcF9kdFtbMV1dICMgQ29lZmZpY2llbnRzIGNvbnRyZSBsYSBzYWxsZSAxDQpgYGANCg0KYGBge3IgVGVtcDgsIGVjaG89RkFMU0V9DQp0ZW1wX2R0W1s0XV0gIyBDb2VmZmljaWVudHMgYWdyw6lnw6lzDQpgYGANCg0KIyBPcHRpbWlzYXRpb24gZGUgbGEgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZQ0KDQpJbCBlc3QgdG91dCDDoCBmYWl0IHBvc3NpYmxlIGQnb3B0aW1pc2VyIGxhIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUsIHBhciB0cm9pcyBjaGVtaW5zIDoNCg0KLSBPcHRpbWlzZXIgbGVzIGh5cGVycGFyYW3DqHRyZXMNCi0gU8OpbGVjdGlvbm5lciBsZSBtZWlsbGV1ciBzdWJzZXQgZGUgZmVhdHVyZXMgw6AgdXRpbGlzZXINCi0gVXRpbGlzZXIgZGUgbWVpbGxldXJlcyBmZWF0dXJlcw0KDQpQYXIgbWFucXVlIGRlIHRlbXBzLCBub3VzIG5lIHRyYXZhaWxsZXJvbnMgcGFzIHN1ciBsJ8OpbGFib3JhdGlvbiBkZSBtZWlsbGV1cmVzIGZlYXR1cmVzLiBBIGxhIHBsYWNlLCBub3VzIG9wdGltaXNlcm9ucyBsZXMgaHlwZXJwYXJhbcOodHJlcyBldCBsZXMgZmVhdHVyZXMgc8OpbGVjdGlvbm7DqWVzLg0KDQojIyBPcHRpbWlzYXRpb24gcGFyIGVudHJvcGllIGNyb2lzw6llDQoNCk5vdXMgYWxsb25zIHV0aWxpc2VyIGwnb3B0aW1pc2F0aW9uIHBhciBlbnRyb3BpZSBjcm9pc8OpZSAoQ3Jvc3MtRW50cm9weSBPcHRpbWl6YXRpb24pLCBxdWkgZG9ubmUgZGVzIHLDqXN1bHRhdHMgcmVtYXJxdWFibGVzIGRhbnMgbGEgcXVhc2kgaW50w6lncmFsaXTDqSBkZXMgY2FzICjDoCBtb2lucyBxdWUgdG91dGVzIGxlcyBmZWF0dXJlcyBldCB0b3VzIGxlcyBoeXBlcnBhcmFtw6h0cmVzIHNvaWVudCBkw6lqw6AgbCd1biBkZXMgbWVpbGxldXJzIHBvc3NpYmxlcykuIEdyw6JjZSDDoCBjZXR0ZSBtw6l0aG9kZSwgbm91cyBwb3V2b25zIDoNCg0KLSBPcHRpbWlzZXIgbGVzIGh5cGVycGFyYW3DqHRyZXMgZGUgbm9zIGNob2l4LCBxdSdpbHMgc29pZW50IGNvbnRpbnVzIG91IGRpc2NyZXRzDQotIFPDqWxlY3Rpb25uZXIgZGVzIGZlYXR1cmVzIChkaXNjcsOpdGlzYXRpb24gYmluYWlyZSBwb3VyIGxhIHPDqWxlY3Rpb24pDQoNCkljaSwgbm91cyBhdm9ucyB0cm9pcyBoeXBlcnBhcmFtw6h0cmVzIGV0IDM2IGZlYXR1cmVzIMOgIHPDqWxlY3Rpb25uZXIgOg0KDQotIGFscGhhIDogcsOpZ3VsYXJpc2F0aW9uIEwxIHN1ciBsZXMgY29lZmZpY2llbnRzLCBxdSdvbiB2YSBjb25zdHJhaW5kcmUgZW50cmUgMCBldCA1DQotIGxhbWJkYSA6IHLDqWd1bGFyaXNhdGlvbiBMMiBzdXIgbGVzIGNvZWZmaWNpZW50cywgcXUnb24gdmEgY29uc3RyYWluZHJlIGVudHJlIDAgZXQgNQ0KLSBsYW1iZGFfYmlhcyA6IHLDqWd1bGFyaXNhdGlvbiBMMiBzdXIgbGUgYmlhaXMsIHF1J29uIHZhIGNvbnN0cmFpbmRyZSBlbnRyZSAwIGV0IDUNCi0gMzYgZmVhdHVyZXMgOiBvbiBzb3VoYWl0ZSByw6lkdWlyZSBkZSA3MiUgZW52aXJvbiBsZSBub21icmUgZGUgZmVhdHVyZXMgcG91ciBxdWUgbGUgbW9kw6hsZSBmaW5hbCBzb2l0IHBlcmZvcm1hbnQgZXQgc2ltcGxlIMOgIGNvbXByZW5kcmUgKGVudmlyb24gMTAgZmVhdHVyZXMpDQoNClBvdXIgbmUgcGFzIHF1ZSBsJ29wdGltaXNldXIgY29udmVyZ2UgdHJvcCByYXBpZGVtZW50LCBub3VzIGFsbG9ucyB1dGlsaXNlciBjZXMgcGFyYW3DqHRyZXMgZCdvcHRpbWlzYXRpb24gOg0KDQotIG9wdGltaXNhdGlvbiBkdSBib29zdGluZyA6IG5vdXMgYWxsb25zIHV0aWxpc2VyIGxhIHBlcnRlIGxvZ2FyaXRobWlxdWUgZXQgbm9uIHBhcyBsJ2luZXhhY3RpdHVkZSBwb3VyIGludGVycm9tcHJlIGxlIGJvb3N0aW5nLCBhZmluIGQnw6l2aXRlciB1biBlZmZldCBkZSBjaGFuY2UgZXQgZmFpcmUgY29udmVyZ2VyIHBsdXMgcmFwaWRlbWVudCBjaGFxdWUgbW9kw6hsZQ0KLSB2YWxldXIgw6Agb3B0aW1pc2VyIDogbWluaW1pc2F0aW9uIGRlIGxhIHBlcnRlIGxvZ2FyaXRobWlxdWUgZGUgY2xhc3NpZmljYXRpb24gKGxvZ2xvc3MpIGV0IG5vbiBwYXMgZGUgbCdpbmV4YWN0aXR1ZGUgZGUgY2xhc3NpZmljYXRpb24gYWZpbiBkJ8Opdml0ZXIgbCdvdmVyZml0dGluZyBpbnZvbG9udGFpcmUgb3UgbCdlZmZldCBkZSBjaGFuY2UgZHVlIMOgIGxhIGZhaWJsZSBxdWFudGl0w6kgZCdvYnNlcnZhdGlvbnMgKG1haXMgw6lnYWxlbWVudCBwb3VyIGZvdXJuaXIgdW5lIMOpY2hlbGxlIGNvbnRpbnVlIGV0IHBhcyBkaXNjcsOodGUgw6AgbCdvcHRpbWlzZXVyIHBvdXIgbGEgcGVydGUpIC0gcG91ciB0ZW50ZXIgZCdvcHRpbWlzZXIgbCdleGFjdGl0dWRlLCBvbiBtdWx0aXBsaWUgbCdpbmV4YWN0aXR1ZGUgcGFyIGxhIHBlcnRlIGxvZ2FyaXRobWlxdWUgcXVpIHNlcmEgcGFzc8OpZSBjb21tZSB2YWxldXIgw6Agb3B0aW1pc2VyIHBhciBlbnRyb3BpZSBjcm9pc8OpZQ0KLSDDqWNoYW50aWxsb25zIHBhciBpdMOpcmF0aW9ucyA6IDI1MCBtb2TDqGxlcyBwb3VyIHVuZSBwb3B1bGF0aW9uIHN0YWJsZSBldCBkaXZlcnNlDQotIGVsaXRlcyA6IDEwJSBkJ2VsaXRlcyBwb3VyIGF2b2lyIHVuZSBwb3B1bGF0aW9uIHBsdXTDtHQgc3RhYmxlIGV0IGRpdmVyc2UNCi0gbm9tYnJlIGQnaXTDqXJhdGlvbnMgOiAyMCBwb3VyIGxhaXNzZXIgw6AgbCdvcHRpbWlzZXVyIGxlIHRlbXBzIGRlIGNoZXJjaGVyIChtYWlzIHRyw6hzIHJhcGlkZW1lbnQpLCBhdmVjIGFycsOqdCBwcsOpbWF0dXLDqSBlbiBjYXMgZGUgc3RhZ25hdGlvbiBkZSBsJ29wdGltaXNhdGlvbiBwZW5kYW50IDUgaXTDqXJhdGlvbnMNCi0gb3B0aW1pc2F0aW9uIGRlcyB2YWxldXJzIGNvbnRpbnVlcyA6IGNvbnZlcmdlbmNlIGxvcnNxdWUgbGVzIGh5cGVycGFyYW3DqHRyZXMgb250IGNoYWN1biB1biDDqWNhcnQtdHlwZSBlbi1kZXNzb3VzIGRlIDAuMTAgZGFucyBsYSBwb3B1bGF0aW9uIMOpbGl0ZQ0KLSBvcHRpbWlzYXRpb24gZGVzIHZhbGV1cnMgZGlzY3LDqHRlcyA6IGNvbnZlcmdlbmNlIGxvcnNxdWUgbGEgc8OpbGVjdGlvbiBkZXMgZmVhdHVyZXMgZXN0IGlkZW50aXF1ZSBkYW5zIGxhIHBvcHVsYXRpb24gw6lsaXRlDQoNClRvdXRlcyBsZXMgdmFyaWFibGVzIG9wdGltaXPDqWVzIHNlcm9udCBsb2dnw6llcyBldCBsZXVycyDDqXZvbHV0aW9ucyBzZXJvbnQgdmlzaWJsZXMgZW4gdGVtcHMgcsOpZWwgdmlhIGRpdmVycyBsb2dpY2llbHMgKGV4ZW1wbGUgOiBCYXJlVGFpbCksIGF2ZWMgbGUgdGFnICIqKioiIGxvcnNxdWUgbCdvcHRpbWlzYXRpb24gZG9ubmUgdW4gbm91dmVhdSBtaW5pbXVtIGxvY2FsLg0KDQpVbmUgcmVjaGVyY2hlIGV4aGF1c3RpdmUgbmUgZm9uY3Rpb25uZXJhIHBhcyBtw6ptZSBhdmVjIHVuIHBldGl0IHN1YnNldCBkZSBmZWF0dXJlcyByZWNoZXJjaMOpZXMgOiBub3VzIHNvbW1lcyBlbiBwcsOpc2VuY2UgZCdoeXBlcnBhcmFtw6h0cmVzIGNvbnRpbnVzIGV0IG5vbiBkaXNjcmV0cy4NCg0KQSBsYSBmaW4sIG5vdXMgYXZvbnMgMTMgZmVhdHVyZXMgYXZlYyB1bmUgbWVpbGxldXJlIHBlcmZvcm1hbmNlIHF1ZSBjZWxsZSBkdSBtb2TDqGxlIGluaXRpYWxlIChqdXNxdSfDoCA3MiUgZCdleGFjdGl0dWRlIGNvbnRyZSA2NSUgaW5pdGlhbGVtZW50KS4gTm90cmUgbW9kw6hsZSBuw6ljZXNzaXRlIGRvbmMgcGx1cyBkZSBmZWF0dXJlcyBxdSdvbiBzb3VoYWl0YWl0ICgzNiUgZGVzIGZlYXR1cmVzIGF1IGxpZXUgZGUgMjglKSwgbWFpcyBsZSBnYWluIGVuIHBlcmZvcm1hbmNlIGVzdCBtYWpldXIgKCs3JSkuIE9uIHBldXQgYW5hbHlzZXIgbGUgbG9nIHBvdXIgZXh0cmFpcmUgbGEgbWVpbGxldXJlIGNvbWJpbmFpc29uIHF1aSBub3VzIHBlcm1ldCBkZSBjb25zZXJ2ZXIgdW5pcXVlbWVudCBkZXMgMTAgZmVhdHVyZXMsIG1haXMgb24gbmUgbGUgZmVyYSBwYXMgcGFyIG1hbnF1ZSBkZSB0ZW1wcyBpY2kuDQoNCmBgYHtyIE9wdGltaXNhdGlvbiwgY2FjaGU9VFJVRX0NCiMgQ29tcHRldXIgZGUgdGVtcHMNCkN1cnJlbnRUaW1lIDwtIHRpbWVyKCkgIyBPcHRpbWlzYXRpb24gcGFyIGVudHJvcGllIGNyb2lzw6llDQoNCkNFX0ZlYXR1cmVzIDwtIGZ1bmN0aW9uKHgsIHksIHRyYWluLCB0ZXN0KSB7DQogIA0KICAjIFByw6ktaW5pdGlhbGlzYXRpb24gZGUgY2VydGFpbmVzIHZhcmlhYmxlcw0KICB0b19rZWVwIDwtIGFzLmJvb2xlYW4oeSkNCiAgaXRlcnMgPDwtIGl0ZXJzICsgMQ0KICANCiAgIyBBdSBtb2lucyB1bmUgZmVhdHVyZSA/DQogIGlmIChzdW0odG9fa2VlcCkgPiAwKSB7DQogICAgDQogICAgZXJyb3IgPC0gbnVtZXJpYygzKQ0KICAgIGxsb3NzIDwtIG51bWVyaWMoMykNCiAgICANCiAgICBmb3IgKGkgaW4gMTA6MTIpIHsNCiAgICAgIA0KICAgICAgdGVtcF9tb2RlbCA8LSB4Z2IudHJhaW4oZGF0YSA9IHhnYi5ETWF0cml4KGRhdGEgPSBhcy5tYXRyaXgodHJhaW5bW2ldXVssIHdoaWNoKHRvX2tlZXApXSksIGxhYmVsID0gdHJhaW5bW2ldXVtbIkxhYmVsIl1dKSwgIyBEb25uw6llcyBkJ2VudHJhaW5lbWVudA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2F0Y2hsaXN0ID0gbGlzdCh0ZXN0ID0geGdiLkRNYXRyaXgoZGF0YSA9IGFzLm1hdHJpeCh0ZXN0W1tpXV1bLCB3aGljaCh0b19rZWVwKV0pLCBsYWJlbCA9IHRlc3RbW2ldXVtbIkxhYmVsIl1dKSksICMgRG9ubsOpZXMgZGUgdmFsaWRhdGlvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtX2NsYXNzID0gNiwgIyA2IGNsYXNzZSBkZSBjbGFzc2lmaWNhdGlvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnRocmVhZCA9IDEsICMgMSB0aHJlYWQgcG91ciBsYSByZXByb2R1Y3Rpb24gZGVzIHLDqXN1bHRhdHMNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3VuZHMgPSAxMDAwLCAjIDEwMDAgaXTDqXJhdGlvbnMsIG/DuSBhcnLDqnQgcHLDqW1hdHVyw6kNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFscGhhID0geFsxXSwgIyBSw6lndWxhcmlzYXRpb24gTDEgKExhc3NvKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFtYmRhID0geFsyXSwgIyBSw6lndWxhcmlzYXRpb24gTDIgKFJpZGdlKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFtYmRhX2JpYXMgPSB4WzNdLCAjIFLDqWd1bGFyaXNhdGlvbiBMMiBkdSBiaWFpcyAoUmlkZ2UpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBldGEgPSAwLjEwLCAjIFNocmlua2FnZSBwb3VyIGxlIGJvb3N0aW5nDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib29zdGVyID0gImdibGluZWFyIiwgIyBUeXBlIGQnZW50cmFpbmVtZW50IDogbGluw6lhaXJlIG91IG5vbi1saW7DqWFpcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9iamVjdGl2ZSA9ICJtdWx0aTpzb2Z0cHJvYiIsICMgR3JhZGllbnQvSGVzc2lhbiBwb3VyIGwnb3B0aW1pc2F0aW9uIHBhciBHcmFkaWVudCBEZXNjZW50DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBldmFsX21ldHJpYyA9ICJtbG9nbG9zcyIsICMgUGVydGUgbG9naWFydGhtaXF1ZSBkZSBsYSBjbGFzc2lmaWNhdGlvbiAoY2V0dGUgbcOpdHJpcXVlIGVzdCBvcHRpbWlzw6llIHBhciB4Z2Jvb3N0KQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXZhbF9tZXRyaWMgPSAibWVycm9yIiwgIyBJbmV4YWN0aXR1ZGUgZGUgbGEgY2xhc3NpZmljYXRpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heGltaXplID0gRkFMU0UsICMgTWluaW1pc2F0aW9uIGRlIGwnZXJyZXVyDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlYXJseV9zdG9wcGluZ19yb3VuZHMgPSA1MCwgIyBBcnLDqnQgYXByw6hzIDUwIGl0w6lyYXRpb25zIHNhbnMgYW3DqWxpb3JhdGlvbiBkZSBsYSBtw6l0cmlxdWUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2UgPSBGQUxTRSwgIyBTYW5zIHByaW50IGRlcyBpdMOpcmF0aW9ucw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2FsbGJhY2tzID0gbGlzdChjYi5ldmFsdWF0aW9uLmxvZygpKSkgIyBMb2dnaW5nIGRlcyBkb25uw6llcyBkJ2VudHJhaW5lbWVudCBwb3VyIHBvdXZvaXIgcsOpY3Vww6lyZXIgbGVzIG3DqXRyaXF1ZXMpDQogICAgICBlcnJvclsxMyAtIGldIDwtIHRlbXBfbW9kZWwkZXZhbHVhdGlvbl9sb2ckdGVzdF9tZXJyb3JbdGVtcF9tb2RlbCRiZXN0X2l0ZXJhdGlvbl0gIyBFbnJlZ2lzdHJlbWVudCBkdSBtZWlsbGV1ciBzY29yZSAoaW5leGFjdGl0dWRlKQ0KICAgICAgbGxvc3NbMTMgLSBpXSA8LSB0ZW1wX21vZGVsJGV2YWx1YXRpb25fbG9nJHRlc3RfbWxvZ2xvc3NbdGVtcF9tb2RlbCRiZXN0X2l0ZXJhdGlvbl0gIyBFbnJlZ2lzdHJlbWVudCBkdSBtZWlsbGV1ciBzY29yZSAocGVydGUgbG9nYXJpdGhtaXF1ZSkNCiAgICAgIA0KICAgIH0NCiAgICANCiAgICAjIEVucmVnaXN0cmVtZW50IGRlIGwnZXJyZXVyIGV0IGxvZ2dpbmcgZGFucyBsJ2Vudmlyb25uZW1udCBnbG9iYWwNCiAgICBlcnJvcl9saXN0W2l0ZXJzLCAyOjRdIDw8LSBlcnJvciAjIEluZXhhY3RpdHVkZQ0KICAgIGVycm9yIDwtIG1lYW4oZXJyb3IpICMgSW5leGFjdGl0dWRlIG1veWVubmUNCiAgICBlcnJvcl9saXN0W2l0ZXJzLCA1XSA8PC0gZXJyb3IgIyBJbmV4YWN0aXR1ZGUgbW95ZW5uZQ0KICAgIGVycm9yX2xpc3RbaXRlcnMsIDY6OF0gPDwtIGxsb3NzICMgUGVydGUgbG9nYXJpdGhtaXF1ZQ0KICAgIGxsb3NzIDwtIG1lYW4obGxvc3MpICMgUGVydGUgbG9nYXJpdGhtaXF1ZSBtb3llbm5lDQogICAgZXJyb3JfbGlzdFtpdGVycywgOV0gPDwtIGxsb3NzICMgUGVydGUgbG9nYXJpdGhtaXF1ZSBtb3llbm5lDQogICAgc2NvcmUgPC0gZXJyb3IgKiBsbG9zcyAjIFNjb3JlIGRlIHBlcnRlDQogICAgZXJyb3JfbGlzdFtpdGVycywgMTBdIDw8LSBzY29yZSAjIFNjb3JlIGRlIHBlcnRlDQogICAgZXJyb3JfbGlzdFtpdGVycywgMTFdIDw8LSBzdW0odG9fa2VlcCkgIyBDb21wdGUgZGUgZmVhdHVyZXMNCiAgICBlcnJvcl9saXN0W2l0ZXJzLCAxMjoxNF0gPDwtIGFzLm51bWVyaWMoeCkgIyBIeXBlcnBhcmFtw6h0cmVzDQogICAgZXJyb3JfbGlzdFtpdGVycywgMTU6NTBdIDw8LSBhcy5udW1lcmljKHRvX2tlZXApICMgRmVhdHVyZXMgdXRpbGlzw6llcw0KICAgIGVycm9yX2xpc3RbaXRlcnMsIDUxXSA8PC0gMQ0KICAgIA0KICAgICMgTGUgcsOpc3VsdGF0IGVzdC1pbCBtZWlsbGV1ciA/IChwb3VyIGxlIGxvZ2dpbmcgZW4gdGVtcHMgcsOpZWwpDQogICAgaWYgKGVycm9yIDwgYmVzdF9lcnJvcikgew0KICAgICAgYmVzdF9lcnJvciA8PC0gZXJyb3INCiAgICAgIHN0YXIgPC0gIigqKiogLSAiDQogICAgfSBlbHNlIHsNCiAgICAgIHN0YXIgPC0gIiggICAgLSAiDQogICAgfQ0KICAgIGlmIChsbG9zcyA8IGJlc3RfbGxvc3MpIHsNCiAgICAgIGJlc3RfbGxvc3MgPDwtIGxsb3NzDQogICAgICBzdGFyIDwtIHBhc3RlMChzdGFyLCAiKioqIC0gIikNCiAgICB9IGVsc2Ugew0KICAgICAgc3RhciA8LSBwYXN0ZTAoc3RhciwgIiAgICAtICIpDQogICAgfQ0KICAgIGlmIChzY29yZSA8IGJlc3Rfc2NvcmUpIHsNCiAgICAgIGJlc3Rfc2NvcmUgPDwtIHNjb3JlDQogICAgICBzdGFyIDwtIHBhc3RlMChzdGFyLCAiKioqKSAiKQ0KICAgIH0gZWxzZSB7DQogICAgICBzdGFyIDwtIHBhc3RlMChzdGFyLCAiICAgKSAiKQ0KICAgIH0NCiAgICANCiAgICAjIExvZ2dpbmcgZW4gdGVtcHMgcsOpZWwNCiAgICBjYXQoc3RhciwgIlsiLCBmb3JtYXQoU3lzLnRpbWUoKSwgIiVYIiksICJdIFBhc3MgIiwgc3ByaW50ZigiJTA1ZCIsIGl0ZXJzKSwgIjogRXJyb3I9Iiwgc3ByaW50ZigiJS4wNWYiLCBlcnJvciksICIgLSBMb3NzPSIsIHNwcmludGYoIiUuMDdmIiwgbGxvc3MpLCAiIC0gU2NvcmU9Iiwgc3ByaW50ZigiJS4wN2YiLCBzY29yZSksICIgLSBmZWF0cz0iLCBzcHJpbnRmKCIlMDRkIiwgc3VtKHRvX2tlZXApKSwgIiAtIGFscGhhPSIsIHNwcmludGYoIiUwNy4wNWYiLCB4WzFdKSwgIiwgbGFtYmRhPSIsIHNwcmludGYoIiUwNy4wNWYiLCB4WzJdKSwgIiwgbGFtYmRhX2JpYXM9Iiwgc3ByaW50ZigiJTA3LjA1ZiIsIHhbM10pLCAiXG4iLCBzZXAgPSAiIiwgZmlsZSA9ICJvcHRpbS9sb2cudHh0IiwgYXBwZW5kID0gVFJVRSkNCiAgICByZXR1cm4oc2NvcmUpDQogICAgDQogIH0gZWxzZSB7DQogICAgDQogICAgIyBMb2dnaW5nIGVuIHRlbXBzIHLDqWVsDQogICAgY2F0KCIoICAgIC0gICAgIC0gICAgKSBbIiwgZm9ybWF0KFN5cy50aW1lKCksICIlWCIpLCAiXSBQYXNzICIsIHNwcmludGYoIiUwNWQiLCBpdGVycyksICI6IGZhaWxlZFxuIiwgc2VwID0gIiIsIGZpbGUgPSAib3B0aW0vbG9nLnR4dCIsIGFwcGVuZCA9IFRSVUUpDQogICAgcmV0dXJuKDkuOTk5OSkNCiAgICANCiAgfQ0KICANCn0NCg0KIyBPw7kgc2F1dmVnYXJkZXIgbGVzIGZpY2hpZXJzID8NCmZpbGVfdGFnIDwtICI0X2RhdGEvIg0KDQojIENyw6lhdGlvbiBkZXMgZG9ubsOpZXMgZCdlbnRyYWluZW1lbnQgZXQgZGUgdmFsaWRhdGlvbg0KZm9yIChpIGluIDE6MTIpIHsNCiAgDQogICMgQ3LDqWF0aW9uIGRlcyBkb25uw6llcyBkJ2VudHJhaW5lbWVudCBldCBkZSB2YWxpZGF0aW9uDQogIHRyYWluaW5nX2RhdGFbW2ldXVtbIkxhYmVsIl1dIDwtIGdyb3VwX3BhdGgkcGF0aF9JRFtmb2xkc190cmFpbltbaV1dXSAtIDENCiAgdGVzdGluZ19kYXRhW1tpXV1bWyJMYWJlbCJdXSA8LSBncm91cF9wYXRoJHBhdGhfSURbZm9sZHNfdGVzdFtbaV1dXSAtIDENCiAgDQp9DQoNCiMgUGFyYW3DqHRyZXMgZGUgbCdvcHRpbWlzZXVyDQpjb250X29wdCA8LSBsaXN0KG1lYW4gPSBjKDEsIDEsIDEpLCAjIETDqWJ1dGUgYXZlYyBlbiBtb3llbm5lLCBBbHBoYT0xLCBMYW1iZGE9MSwgTGFtYmRhX2JpYXM9MQ0KICAgICAgICAgICAgICAgICBzZCA9IGMoMSwgMSwgMSksICMgRMOpYnV0ZSBhdmVjIGVuIMOpY2FydC10eXBlLCBBbHBoYT0xLCBMYW1iZGE9MSwgTGFtYmRhX2JpYXM9MQ0KICAgICAgICAgICAgICAgICBjb25NYXQgPSByYmluZChkaWFnKDMpLCAtZGlhZygzKSksICMgT3B0aW1pc2F0aW9uIGxpbsOpYWlyZSBjb25kaXRpb25uw6llIHBhciBsYSBtYXRyaWNlIGR1IHNpbXBsZXhlDQogICAgICAgICAgICAgICAgIGNvblZlYyA9IGMoNSwgNSwgNSwgMCwgMCwgMCksICMgMDw9YWxwaGE8PTUsIDA8PUxhbWJkYTw9NSwgMDw9TGFtYmRhX2JpYXM8PTUNCiAgICAgICAgICAgICAgICAgc2RUaHIgPSAwLjEpICMgT24gc3VwcG9zZSBsZXMgaHlwZXJwYXJhbcOodHJlcyBjb252ZXJnw6lzIGxvcnNxdWUgdG91cyBsZXMgw6ljYXJ0LXR5cGVzIHNvbnQgZW4tZGVzc291cyBkZSAwLjENCnAwIDwtIGxpc3QoKSAjIFByw6ktaW5pdGFpc2xpYXRpb24gZGUgbGEgbGlzdGUgcG91ciBsZXMgdmFyaWFibGVzIGRpc2Nyw6h0ZXMNCmZvciAoaSBpbiAxOjM2KSB7cDAgPC0gYyhwMCwgbGlzdChjKDAuNzIsIDAuMjgpKSl9ICMgT24gc291aGFpdGUgNTAlIGRlcyBmZWF0dXJlcyDDoCBsYSBmaW4gZW4gbW95ZW5uZSwgw6AgbW9pbnMgcXVlIGNlcnRhaW5lcyB2YXJpYWJsZXMgb250IHVuZSBpbXBvcnRhbmNlIHRlbGxlIHF1J2VsbGVzIG5lIHBldXZlbnQgw6p0cmUgb21pc2VzIGV0IHNlcm9udCBmb3Jjw6ltZW50IHPDqWxlY3Rpb25uw6llcw0KZGlzY19vcHQgPC0gbGlzdChwcm9icyA9IHAwLA0KICAgICAgICAgICAgICAgICBzbW9vdGhQcm9iID0gMS4wMCwgIyBPbiB2YSB0ZW50ZXIgZGUgY29udmVyZ2VyIHJhcGlkZW1lbnQgaWNpIHBvdXIgbGEgcsOpYWxpc3Rpb24gZCd1biBwcm9vZiBvZiBjb25jZXB0LCBtYWlzIHNpbm9uIGF2ZWMgcGx1cyBkZSB0ZW1wcyBvbiBwb3VycmEgcsOpYWxpc2VyIHVuIHNocmlua2FnZSBkZSA1JSBkZSBsYSBwcm9iYWJpbGl0w6kgw6lsaXRlIMOgIGNoYXF1ZSBpdMOpcmF0aW9uIChzbW9vdGhQcm9iID0gMC45NSkNCiAgICAgICAgICAgICAgICAgcHJvYlRociA9IDAuMDAwMSkgIyBPbiBzdXBwb3NlIGxhIHPDqWxlY3Rpb24gZGUgZmVhdHVyZXMgY29udmVyZ8OpIGxvcnNxdWUgdG91dGVzIGxlcyBwcm9iYWJpbGl0w6lzIHNvbnQgZW4tZGVzc291cyBkZSAwLjAwMDENCm5fZmFtaWx5IDwtIDI1MCAjIExlIG5vbWJyZSBkJ2VzdGltYXRpb25zIHBhciBpdMOpcmF0aW9uIGRlIGwnb3B0aW1pc2V1cg0KZWxpdGUgPC0gMC4xICMgTGUgbm9tYnJlIGQnw6lsaXRlcyBwYXIgaXTDqXJhdGlvbiBxdWkgZGljdGVudCBsYSBsb2kgZGFucyBsJ2VudHJvcGllIGNyb2lzw6llDQppdGVyYXRpb25zIDwtIDIxICMgTGUgbm9tYnJlIGQnaXTDqXJhdGlvbnMgZCdvcHRpbWlzYXRpb24gKHBsdXMgdW4gcG91ciBsJ2l0w6lyYXRpb24gZCdpbml0aWFsaXNhdGlvbikNCmVhcmx5X3N0b3AgPC0gNSAjIEFycsOqdCBwcsOpbWF0dXLDqSBsb3JzcXVlIGxhIGZvbmN0aW9uIGRlIHBlcnRlIChpY2kgbCdpbmV4YWN0aXR1ZGUgZGUgbGEgY2xhc3NpZmljYXRpb24pIG5lIGRpbWludWUgcGFzIGFwcsOocyBYIGl0w6lyYXRpb25zDQoNCiMgUHLDqS1pbml0aWFsaXNhdGlvbiBkZSBsYSB2YXJpYWJsZSBkZSBsb2dnaW5nDQppdGVycyA8LSAwICMgU3VpdmkgZGUgbCdpdMOpcmF0aW9uDQpiZXN0X2Vycm9yIDwtIDEgIyBTdWl2aSBkZSBsYSBwZXJ0ZSAoaW5leGFjdGl0dWRlKSwgaW5pdGlhbGlzw6kgw6AgdW5lIHRyw6hzIG1hdXZhaXNlIHZhbGV1ciBwb3NzaWJsZQ0KYmVzdF9sbG9zcyA8LSA5Ljk5OTkgIyBTdWl2aSBkZSBsYSBwZXJ0ZSAobG9nYXJpdGhtaXF1ZSksIGluaXRpYWxpc8OpIMOgIHVuZSB0csOocyBtYXV2YWlzZSB2YWxldXIgcG9zc2libGUNCmJlc3Rfc2NvcmUgPC0gOS45OTk5ICMgU3VpdmkgZGUgbGEgcGVydGUgKGluZXhhY3RpdHVkZSAqIGxvZ2FyaXRobWlxdWUpLCBpbml0aWFsaXPDqSDDoCB1bmUgdHLDqHMgbWF1dmFpc2UgdmFsZXVyIHBvc3NpYmxlDQplcnJvcl9saXN0IDwtIGRhdGEuZnJhbWUoSXRlcmF0aW9uID0gMToobl9mYW1pbHkgKiBpdGVyYXRpb25zKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBFcnJvcl8xID0gbnVtZXJpYyhuX2ZhbWlseSAqIGl0ZXJhdGlvbnMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIEVycm9yXzIgPSBudW1lcmljKG5fZmFtaWx5ICogaXRlcmF0aW9ucyksDQogICAgICAgICAgICAgICAgICAgICAgICAgRXJyb3JfMyA9IG51bWVyaWMobl9mYW1pbHkgKiBpdGVyYXRpb25zKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBFcnJvcl9NZWFuID0gbnVtZXJpYyhuX2ZhbWlseSAqIGl0ZXJhdGlvbnMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIExvc3NfMSA9IG51bWVyaWMobl9mYW1pbHkgKiBpdGVyYXRpb25zKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBMb3NzXzIgPSBudW1lcmljKG5fZmFtaWx5ICogaXRlcmF0aW9ucyksDQogICAgICAgICAgICAgICAgICAgICAgICAgTG9zc18zID0gbnVtZXJpYyhuX2ZhbWlseSAqIGl0ZXJhdGlvbnMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIExvc3NfTWVhbiA9IG51bWVyaWMobl9mYW1pbHkgKiBpdGVyYXRpb25zKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBTY29yZSA9IG51bWVyaWMobl9mYW1pbHkgKiBpdGVyYXRpb25zKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGZWF0dXJlc19uID0gbnVtZXJpYyhuX2ZhbWlseSAqIGl0ZXJhdGlvbnMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIEFscGhhID0gbnVtZXJpYyhuX2ZhbWlseSAqIGl0ZXJhdGlvbnMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIExhbWJkYSA9IG51bWVyaWMobl9mYW1pbHkgKiBpdGVyYXRpb25zKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBMYW1iZGFfYmlhcyA9IG51bWVyaWMobl9mYW1pbHkgKiBpdGVyYXRpb25zKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBtYXRyaXgocmVwKDAsIG5fZmFtaWx5ICogaXRlcmF0aW9ucyAqIDM2KSwgbmNvbCA9IDM2KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBMb2dnaW5nID0gcmVwKDAsIG5fZmFtaWx5ICogaXRlcmF0aW9ucykpDQpjb2xuYW1lcyhlcnJvcl9saXN0KVsxNTo1MF0gPC0gYyhwYXN0ZTAocmVwKGMocGFzdGUwKCJDb2VmIiwgMTo0KSwgcGFzdGUwKCJSw6lzaSIsIDE6NCkpLCA0KSwgcGFzdGUwKCJfIiwgaW52ZXJzZS5ybGUobGlzdChsZW5ndGhzID0gcmVwKDgsIDQpLCB2YWx1ZXMgPSAxOjQpKSkpLCBwYXN0ZTAoIlBvc0luaXRpYWxlXyIsIDE6NCkpDQoNCnNldC5zZWVkKDApICMgRml4YXRpb24gZHUgc2VlZCBhbMOpYXRvaXJlIHBvdXIgZGVzIHLDqXN1bHRhdHMgcXVpIHB1aXNzZW50IMOqdHJlIHJlcHJvZHVpdHMNCg0KIyBPcHRpbWlzYXRpb24gcGFyIGVudHJvcGllIGNyb2lzw6llDQpiZXN0X3dlaWdodHMgPC0gQ0VvcHRpbShDRV9GZWF0dXJlcywNCiAgICAgICAgICAgICAgICAgICAgICAgIGYuYXJnID0gbGlzdCh0cmFpbiA9IHRyYWluaW5nX2RhdGEsICMgRG9ubsOpZXMgZCdlbnRyYWluZW1lbnQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdGluZ19kYXRhKSwgIyBEb25uw6llcyBkZSB2YWxpZGF0aW9uDQogICAgICAgICAgICAgICAgICAgICAgICBtYXhpbWl6ZSA9IEZBTFNFLCAjIE1pbmltaXNhdGlvbiBkdSBwcm9ibMOobWUNCiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRpbnVvdXMgPSBjb250X29wdCwNCiAgICAgICAgICAgICAgICAgICAgICAgIGRpc2NyZXRlID0gZGlzY19vcHQsDQogICAgICAgICAgICAgICAgICAgICAgICBOID0gbl9mYW1pbHksDQogICAgICAgICAgICAgICAgICAgICAgICByaG8gPSBlbGl0ZSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2UgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgaXRlclRociA9IGl0ZXJhdGlvbnMgLSAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgbm9JbXByb3ZlVGhyID0gZWFybHlfc3RvcCkNCg0KIyBFbnJlZ2lzdHJlbWVudCBkdSBsb2cgZMOpdGFpbGzDqQ0KZndyaXRlKGVycm9yX2xpc3QsICJvcHRpbS9lcnJvcl9yYXcuY3N2IikNCg0KIyBFbnJlZ2lzdHJlbWVudCBkdSBsb2cgZMOpdGFpbGzDqSBldCBuZXR0b3nDqSBkZXMgw6lsw6ltZW50cyBpbnV0aWxlcw0KZXJyb3JfbGlzdCA8LSBlcnJvcl9saXN0W2Vycm9yX2xpc3QkTG9nZ2luZyA9PSAxLCBdDQpmd3JpdGUoZXJyb3JfbGlzdCwgIm9wdGltL2Vycm9yX2NsZWFuLmNzdiIpDQoNCiMgRW5yZWdpc3RyZW1lbnQgZGUgbGEgdmFyaWJsZSBjb250ZW5hbnQgbCdvcHRpbWlzYXRpb24NCnNhdmVSRFMoYmVzdF93ZWlnaHRzLCAib3B0aW0vb3B0aW1pemVkLnJkcyIpDQoNCiMgQWZmaWNoYWdlIGRlcyByw6lzdWx0YXRzDQp4IDwtIGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkY29udGludW91cyAjIFLDqWN1cMOpcmF0aW9uIGRlcyBoeXBlcnBhcmFtw6h0cmVzDQp5IDwtIGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkZGlzY3JldGUgIyBSw6ljdXDDqXJhdGlvbiBkZXMgZmVhdHVyZXMgc8OpbGVjdGlvbm7DqWVzDQpjYXQoIiAgXG5MJ29wdGltaXNldXIgYSB0cm91dsOpIDogIFxuICAtIE1laWxsZXVyZSBJbmV4YWN0aXR1ZGUgPSAiLCBiZXN0X2Vycm9yLCAiICBcbiAgLSBNZWlsbGV1cmUgUGVydGUgTG9nYXJpdGhtaXF1ZSA9ICIsIGJlc3RfbGxvc3MsICJcbiAgLSBhbHBoYSA9ICIsIHhbMV0sICIgIFxuICAtIGxhbWJkYSA9ICIsIHhbMl0sICIgIFxuIC0gbGFtYmRhX2JpYXMgPSAiLCB4WzNdLCAiICBcbiAgLSBmZWF0dXJlcyA9ICIsIHN1bShhcy5ib29sZWFuKHkpKSwgIiAoYmluYXJ5ID0gIiwgcGFzdGUoeSwgY29sbGFwc2UgPSAiIiksICIpICBcbiAgXG5GZWF0dXJlcyB1dGlsaXPDqWVzIDogIFxuIiwgc2VwID0gIiIpDQpkcHV0KGMocGFzdGUwKHJlcChjKHBhc3RlMCgiQ29lZiIsIDE6NCksIHBhc3RlMCgiUsOpc2kiLCAxOjQpKSwgNCksIHBhc3RlMCgiXyIsIGludmVyc2UucmxlKGxpc3QobGVuZ3RocyA9IHJlcCg4LCA0KSwgdmFsdWVzID0gMTo0KSkpKSwgcGFzdGUwKCJQb3NJbml0aWFsZV8iLCAxOjQpKVthcy5ib29sZWFuKHkpXSkgIyBBZmZpY2hhZ2UgZGVzIG5vbXMgZGVzIGZlYXR1cmVzDQoNCiMgVGVtcHMgbsOpY2Vzc2FpcmUNCnRpbWluZyhDdXJyZW50VGltZSwgIk9wdGltaXNhdGlvbiBwYXIgZW50cm9waWUgY3JvaXPDqWUiKQ0KYGBgDQoNCiMjIFZpc3VhbGlzYXRpb24gZGUgbCfDqXZvbHV0aW9uIGRlIGwnb3B0aW1pc2F0aW9uDQoNCk9uIHBldXQgdmlzdWFsaXNlciBsJ8Opdm9sdXRpb24gZGUgbCdvcHRpbWlzYXRpb24gw6AgcGFydGlyIGRlIGdyYXBoaXF1ZXMuIE9uIHJlbWFycXVlIHVuZSBjb252ZXJnZW5jZSBwbHV0w7R0IHJhcGlkZSBkZXMgcGVydGVzIChpbmV4YWN0aXR1ZGUsIGxvZ2xvc3MpLg0KDQpPbiByZW1hcnF1ZXJhIGwnaW52ZXJzaW9uIGVudHJlIGR1IG5pdmVhdSBkZXMgY291cmJlcyBkZXMgc2FsbGVzIDEgZXQgMiBsb3JzcXUnb24gb3Bwb3NlIHBlcnRlIGxvZ2FyaXRobWlxdWUgZXQgaW5leGFjdGl0dWRlLiBFbiBlZmZldCwgaWwgbidleGlzdGUgYXVjdW5lIGNvcnLDqWxhdGlvbiBkaXJlY3RlIGVudHJlIGxhIHBlcnRlIGxvZ2FyaXRobWlxdWUgZXQgbCdpbmV4YWN0aXR1ZGUuDQoNCmBgYHtyIFZpc09wdGltaXNhdGlvbn0NCmdncGxvdGx5KGdncGxvdChkYXRhID0gZXJyb3JfbGlzdFssIGMoIkl0ZXJhdGlvbiIsICJFcnJvcl9NZWFuIildLCBhZXNfc3RyaW5nKHggPSAiSXRlcmF0aW9uIiwgeSA9ICJFcnJvcl9NZWFuIikpICsgZ2VvbV9saW5lKCkgKyB0aGVtZV9idygpICsgbGFicyh0aXRsZSA9ICJFdm9sdXRpb24gZGUgbCdpbmV4YWN0aXR1ZGUgcGFyIHJhcHBvcnQgYXUgbm9tYnJlIGQnaXTDqXJhdGlvbnMgZGUgbW9kw6lsaXNhdGlvbnMgZW4gbW95ZW5uZSIpLCB3aWR0aCA9IDk2MCwgaGVpZ2h0ID0gNzIwKQ0KZ2dwbG90bHkoZ2dwbG90KGRhdGEgPSBkYXRhLmZyYW1lKEl0ZXJhdGlvbiA9IHJlcCgxOm5yb3coZXJyb3JfbGlzdCksIDMpLCBFcnJvciA9IGMoZXJyb3JfbGlzdFtbIkVycm9yXzEiXV0sIGVycm9yX2xpc3RbWyJFcnJvcl8yIl1dLCBlcnJvcl9saXN0W1siRXJyb3JfMyJdXSksIFNhbGxlID0gYXMuZmFjdG9yKGludmVyc2UucmxlKGxpc3QobGVuZ3RocyA9IHJlcChucm93KGVycm9yX2xpc3QpLCAzKSwgdmFsdWVzID0gMTozKSkpKSwgYWVzX3N0cmluZyh4ID0gIkl0ZXJhdGlvbiIsIHkgPSAiRXJyb3IiLCBjb2xvciA9ICJTYWxsZSIpKSArIGdlb21fbGluZSgpICsgdGhlbWVfYncoKSArIGxhYnModGl0bGUgPSAiRXZvbHV0aW9uIGRlIGwnaW5leGFjdGl0dWRlIHBhciByYXBwb3J0IGF1IG5vbWJyZSBkJ2l0w6lyYXRpb25zIGRlIG1vZMOpbGlzYXRpb24gcGFyIHNhbGxlIiksIHdpZHRoID0gOTYwLCBoZWlnaHQgPSA3MjApDQpnZ3Bsb3RseShnZ3Bsb3QoZGF0YSA9IGVycm9yX2xpc3RbLCBjKCJJdGVyYXRpb24iLCAiTG9zc19NZWFuIildLCBhZXNfc3RyaW5nKHggPSAiSXRlcmF0aW9uIiwgeSA9ICJMb3NzX01lYW4iKSkgKyBnZW9tX2xpbmUoKSArIHRoZW1lX2J3KCkgKyBsYWJzKHRpdGxlID0gIkV2b2x1dGlvbiBkdSBsb2dsb3NzIHBhciByYXBwb3J0IGF1IG5vbWJyZSBkJ2l0w6lyYXRpb25zIGRlIG1vZMOpbGlzYXRpb25zIGVuIG1veWVubmUiKSwgd2lkdGggPSA5NjAsIGhlaWdodCA9IDcyMCkNCmdncGxvdGx5KGdncGxvdChkYXRhID0gZGF0YS5mcmFtZShJdGVyYXRpb24gPSByZXAoMTpucm93KGVycm9yX2xpc3QpLCAzKSwgTG9zcyA9IGMoZXJyb3JfbGlzdFtbIkxvc3NfMSJdXSwgZXJyb3JfbGlzdFtbIkxvc3NfMiJdXSwgZXJyb3JfbGlzdFtbIkxvc3NfMyJdXSksIFNhbGxlID0gYXMuZmFjdG9yKGludmVyc2UucmxlKGxpc3QobGVuZ3RocyA9IHJlcChucm93KGVycm9yX2xpc3QpLCAzKSwgdmFsdWVzID0gMTozKSkpKSwgYWVzX3N0cmluZyh4ID0gIkl0ZXJhdGlvbiIsIHkgPSAiTG9zcyIsIGNvbG9yID0gIlNhbGxlIikpICsgZ2VvbV9saW5lKCkgKyB0aGVtZV9idygpICsgbGFicyh0aXRsZSA9ICJFdm9sdXRpb24gZHUgbG9nbG9zcyBwYXIgcmFwcG9ydCBhdSBub21icmUgZCdpdMOpcmF0aW9ucyBkZSBtb2TDqWxpc2F0aW9uIHBhciBzYWxsZSIpLCB3aWR0aCA9IDk2MCwgaGVpZ2h0ID0gNzIwKQ0KYGBgDQoNCiMjIENyw6lhdGlvbiBkZXMgZmVhdHVyZXMNCg0KTm91cyBwb3V2b25zIGNyw6llciBsZXMgZmVhdHVyZXMgw6AgcGFydGlyIGRlcyBmZWF0dXJlcyBzw6lsZWN0aW9ubsOpZXMuDQoNCmBgYHtyIEdlbmVyYXRpb25Eb25uZWVzNH0NCiMgQ29tcHRldXIgZGUgdGVtcHMNCkN1cnJlbnRUaW1lIDwtIHRpbWVyKCkgIyBQcsOpcGFyYXRpb24gZGUgbCfDqXZhbHVhdGlvbiBkZXMgbW9kw6hsZXMgYXZlYyBmZWF0dXJlcyBzw6lsZWN0aW9ubsOpZXMNCg0KIyBPw7kgc2F1dmVnYXJkZXIgbGVzIGZpY2hpZXJzID8NCmZpbGVfdGFnIDwtICI0X2RhdGEvIg0KDQojIEluaXRpYWxpc2F0aW9uIGRlIGxhIHZhcmlhYmxlIHF1aSBhY2N1ZWlsbGVyYSBsYSBwcsOpY2lzaW9uDQphY2N1cmFjeSA8LSBkYXRhLmZyYW1lKG1hdHJpeChucm93ID0gMTYsIG5jb2wgPSAxMykpDQpjb2xuYW1lcyhhY2N1cmFjeSkgPC0gYygiRm9sZCIsICJ4Z2JfTGluZWFyTW9kZWwiLCAieGdiX0RlY2lzaW9uVHJlZSIsICJ4Z2JfUmFuZG9tRm9yZXN0IiwgInhnYl9HcmFkaWVudEJvb3N0aW5nIiwgImgyb19MaW5lYXJNb2RlbCIsICJoMm9fRGVjaXNpb25UcmVlIiwgImgyb19SYW5kb21Gb3Jlc3QiLCAiaDJvX0dyYWRpZW50Qm9vc3RpbmciLCAiaDJvX05OXzMyeDZfUmVMVSIsICJoMm9fTk5fMzJ4Nl9Tb2Z0IiwgImgyb19OTl8xNngxNng2X1JlTFUiLCAiaDJvX05OXzE2eDE2eDZfU29mdCIpDQphY2N1cmFjeVssIDFdIDwtIGMoIkZvbGRfMXYyIiwgIkZvbGRfMXYzIiwgIkZvbGRfMnYxIiwgIkZvbGRfMnYzIiwgIkZvbGRfM3YxIiwgIkZvbGRfM3YyIiwgIkZvbGRfMXYyMyIsICJGb2xkXzJ2MTMiLCAiRm9sZF8zdjEyIiwgIkZvbGRfMTJ2MyIsICJGb2xkXzEzdjIiLCAiRm9sZF8yM3YxIiwgIk1veWVubmVfMWMxIiwgIk1veWVubmVfMWMyIiwgIk1veWVubmVfMmMxIiwgIk1veWVubmUiKQ0KDQojIEluaXRpYWxpc2F0aW9uIGRlcyBmb2xkcyBwb3VyIGxhIGNyb3NzLXZhbGlkYXRpb24NCmZvbGRzX3RyYWluIDwtIGxpc3QoKQ0KZm9sZHNfdGVzdCA8LSBsaXN0KCkNCnRyYWluaW5nX2RhdGEgPC0gbGlzdCgpDQp0ZXN0aW5nX2RhdGEgPC0gbGlzdCgpDQp0cmFpbmluZ194Z2IgPC0gbGlzdCgpDQp0ZXN0aW5nX3hnYiA8LSBsaXN0KCkNCnRyYWluaW5nX2gybyA8LSBsaXN0KCkNCnRlc3RpbmdfaDJvIDwtIGxpc3QoKQ0KY29tYmluYXRpb25zX3RyYWluIDwtIGMobGlzdCgxLCAxLCAyLCAyLCAzLCAzKSwgY29tYm4oMywgMSwgc2ltcGxpZnkgPSBGQUxTRSksIGNvbWJuKDMsIDIsIHNpbXBsaWZ5ID0gRkFMU0UpKQ0KY29tYmluYXRpb25zX3Rlc3QgPC0gYyhsaXN0KDIsIDMsIDEsIDMsIDEsIDIpLCByZXYoY29tYm4oMywgMiwgc2ltcGxpZnkgPSBGQUxTRSkpLCByZXYoY29tYm4oMywgMSwgc2ltcGxpZnkgPSBGQUxTRSkpKQ0KdGVtcF9mYWN0b3JzIDwtIGFzLmZhY3Rvcihncm91cF9wYXRoJHBhdGhfSUQpDQoNCiMgQ3LDqWF0aW9uIGRlcyBkb25uw6llcyBkJ2VudHJhaW5lbWVudCBldCBkZSB2YWxpZGF0aW9uDQpmb3IgKGkgaW4gMToxMikgew0KICANCiAgIyBDcsOpYXRpb24gZGVzIGZvbGRzIGQnZW50cmFpbmVtZW50IGV0IGRlIHZhbGlkYXRpb24NCiAgZm9sZHNfdHJhaW5bW2ldXSA8LSB3aGljaChncm91cF9yb29tW1siZGF0YXNldF9JRCJdXSAlaW4lIGNvbWJpbmF0aW9uc190cmFpbltbaV1dKQ0KICBmb2xkc190ZXN0W1tpXV0gPC0gd2hpY2goZ3JvdXBfcm9vbVtbImRhdGFzZXRfSUQiXV0gJWluJSBjb21iaW5hdGlvbnNfdGVzdFtbaV1dKQ0KICANCiAgIyBSZWNoZXJjaGUgZXQgc3VwcHJlc3Npb24gZHUgbGFiZWwgMyBsb3JzcXVlIGxhIHNhbGxlIDEgZXN0IGlzb2zDqWUgKHNvaXQgZW4gdHJhaW4gb24gZW5sw6h2ZSBlbiB0ZXN0LCBzb2l0IGVuIHRlc3Qgb24gZW5sw6h2ZSBlbiB0cmFpbikNCiAgaWYgKChsZW5ndGgoY29tYmluYXRpb25zX3RyYWluW1tpXV0pID09IDEpICYgKGNvbWJpbmF0aW9uc190cmFpbltbaV1dWzFdID09IDEpKSB7DQogICAgZm9sZHNfdGVzdFtbaV1dIDwtIGZvbGRzX3Rlc3RbW2ldXVtncm91cF9wYXRoJHBhdGhfSURbZm9sZHNfdGVzdFtbaV1dXSAhPSAzXQ0KICB9DQogIGlmICgobGVuZ3RoKGNvbWJpbmF0aW9uc190ZXN0W1tpXV0pID09IDEpICYgKGNvbWJpbmF0aW9uc190ZXN0W1tpXV1bMV0gPT0gMSkpIHsNCiAgICBmb2xkc190cmFpbltbaV1dIDwtIGZvbGRzX3RyYWluW1tpXV1bZ3JvdXBfcGF0aCRwYXRoX0lEW2ZvbGRzX3RyYWluW1tpXV1dICE9IDNdDQogIH0NCiAgDQogICMgQ3LDqWF0aW9uIGRlcyBkb25uw6llcyBkJ2VudHJhaW5lbWVudCBldCBkZSB2YWxpZGF0aW9uDQogIHRyYWluaW5nX2RhdGFbW2ldXSA8LSBtaW5pX2xtW2ZvbGRzX3RyYWluW1tpXV0sIHdoaWNoKGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkZGlzY3JldGUgPT0gMSldDQogIHRlc3RpbmdfZGF0YVtbaV1dIDwtIG1pbmlfbG1bZm9sZHNfdGVzdFtbaV1dLCB3aGljaChiZXN0X3dlaWdodHMkb3B0aW1pemVyJGRpc2NyZXRlID09IDEpXQ0KICANCiAgIyBFbnJlZ2lzdHJlbWVudCBkZXMgZG9ubsOpZXMgQ1NWDQogIGZ3cml0ZSh0cmFpbmluZ19kYXRhW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRyYWluTkxfIiwgc3ByaW50ZigiJTAyZCIsIGkpLCAiLmNzdiIpKQ0KICBmd3JpdGUodGVzdGluZ19kYXRhW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRlc3ROTF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuY3N2IikpDQogIA0KICAjIFRyYW5zZm9ybWF0aW9uIGRlcyBkb25uw6llcyBhdSBmb3JtYXQgYXBwcm9wcmnDqSBwb3VyIHhnYm9vc3QNCiAgdHJhaW5pbmdfeGdiW1tpXV0gPC0geGdiLkRNYXRyaXgoZGF0YSA9IGFzLm1hdHJpeCh0cmFpbmluZ19kYXRhW1tpXV0pLCBsYWJlbCA9IGdyb3VwX3BhdGgkcGF0aF9JRFtmb2xkc190cmFpbltbaV1dXSAtIDEpDQogIHRlc3RpbmdfeGdiW1tpXV0gPC0geGdiLkRNYXRyaXgoZGF0YSA9IGFzLm1hdHJpeCh0ZXN0aW5nX2RhdGFbW2ldXSksIGxhYmVsID0gZ3JvdXBfcGF0aCRwYXRoX0lEW2ZvbGRzX3Rlc3RbW2ldXV0gLSAxKQ0KICANCiAgIyBEdW1waW5nIGRlcyBkYXRhc2V0cyBiaW5haXJlcyB4Z2Jvb3N0DQogIHhnYi5ETWF0cml4LnNhdmUodHJhaW5pbmdfeGdiW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRyYWluTF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuZGF0YSIpKQ0KICB4Z2IuRE1hdHJpeC5zYXZlKHRlc3RpbmdfeGdiW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRlc3RMXyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5kYXRhIikpDQogIA0KICAjIFRyYW5zZm9ybWF0aW9uIGRlcyBkb25uw6llcyBhdSBmb3JtYXQgYXBwcm9wcmnDqSBwb3VyIEgyTw0KICB0cmFpbmluZ19oMm9bW2ldXSA8LSBhcy5oMm8oY2JpbmQoTGFiZWwgPSB0ZW1wX2ZhY3RvcnNbZm9sZHNfdHJhaW5bW2ldXV0sIHRyYWluaW5nX2RhdGFbW2ldXSkpDQogIHRlc3RpbmdfaDJvW1tpXV0gPC0gYXMuaDJvKGNiaW5kKExhYmVsID0gdGVtcF9mYWN0b3JzW2ZvbGRzX3Rlc3RbW2ldXV0sIHRlc3RpbmdfZGF0YVtbaV1dKSkNCiAgDQogICMgRW5yZWdpc3RyZW1lbnQgZGVzIGZyYW1lcyBIMk8gKENTViArIExhYmVsKQ0KICBoMm8uZXhwb3J0RmlsZSh0cmFpbmluZ19oMm9bW2ldXSwgcGFzdGUwKGZpbGVfdGFnLCAidHJhaW5MXyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5jc3YiKSwgZm9yY2UgPSBUUlVFKQ0KICBoMm8uZXhwb3J0RmlsZSh0ZXN0aW5nX2gyb1tbaV1dLCBwYXN0ZTAoZmlsZV90YWcsICJ0ZXN0TF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuY3N2IiksIGZvcmNlID0gVFJVRSkNCiAgDQp9DQoNCiMgVGVtcHMgbsOpY2Vzc2FpcmUNCnRpbWluZyhDdXJyZW50VGltZSwgIlByw6lwYXJhdGlvbiBkZSBsJ8OpdmFsdWF0aW9uIGRlcyBtb2TDqGxlcyBhdmVjIGZlYXR1cmVzIHPDqWxlY3Rpb25uw6llcyIpDQpgYGANCg0KIyMgRW50cmFpbmVtZW50IGRlcyBkb3V6ZSBtb2TDqGxlcw0KDQpOb3VzIHBvdXZvbnMgbWFpbnRlbmFudCBlbnRyYWluZXIgbGVzIG1vZMOobGVzIHN1ciBub3RyZSBzw6lsZWN0aW9uIGRlIGZlYXR1cmVzLg0KDQpgYGB7ciBFbnRyYWluZW1lbnQ0LCBjYWNoZT1UUlVFfQ0KIyBDb21wdGV1ciBkZSB0ZW1wcw0KQ3VycmVudFRpbWUgPC0gdGltZXIoKSAjIENodW5rIENyw6lhdGlvbiBldCDDqXZhbHVhdGlvbiBkZXMgZG91emUgbW9kw6hsZXMgYXZlYyBmZWF0dXJlcyBzw6lsZWN0aW9ubsOpZXMNCg0KIyBPw7kgc2F1dmVnYXJkZXIgbGVzIGZpY2hpZXJzID8NCmZpbGVfdGFnIDwtICI0X21vZGVscy8iDQpmaWxlX2gybyA8LSAiNF9tb2RlbHMiDQoNCiMgQm91Y2xlIGQnw6l2YWx1YXRpb24NCmZvciAoaSBpbiAxOjEyKSB7DQogIA0KICAjIEVudHJhaW5lbWVudCBkdSBtb2TDqGxlIGRlIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUgKHhnYm9vc3QpDQogIHRlbXBfbW9kZWwgPC0geGdiX2R5bmFtaWNfdHJhaW4odHJhaW4gPSB0cmFpbmluZ194Z2JbW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdGluZ194Z2JbW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib29zdGVyID0gImdibGluZWFyIiwgIyBMaW7DqWFpcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBucm91bmRzID0gMTAwMDAwMCwgIyBBcnLDqnTDqSBhdSBtZWlsbGV1ciByw6lzdWx0YXQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1fcGFyYWxsZWxfdHJlZXMgPSAxKQ0KICB4Z2IuZHVtcChtb2RlbCA9IHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICBmbmFtZSA9IHBhc3RlMChmaWxlX3RhZywgInhnYl9nbG1fIiwgc3ByaW50ZigiJTAyZCIsIGkpLCAiLmpzb24iKSwgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgIHdpdGhfc3RhdHMgPSBUUlVFLCAjIEVucmVnaXN0cmVtZW50IGRlcyBzdGF0aXN0aXF1ZXMgc2kgbW9kw6hsZSBnYnRyZWUNCiAgICAgICAgICAgZHVtcF9mb3JtYXQgPSAianNvbiIpICMgRHVtcCBhdSBmb3JtYXQganNvbiwgcsOpLXV0aWxpc2FibGUNCiAgYWNjdXJhY3lbaSwgMl0gPC0gMSAtIHRlbXBfbW9kZWwkZXZhbHVhdGlvbl9sb2dbWzJdXVt0ZW1wX21vZGVsJGJlc3RfaXRlcmF0aW9uXSAjIFLDqWN1cMOpcmF0aW9uIGR1IG1laWxsZXVyIHLDqXN1bHRhdA0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbW9kw6hsZSBkJ2FyYnJlIGRlIGTDqWNpc2lvbiAoeGdib29zdCkNCiAgdGVtcF9tb2RlbCA8LSB4Z2JfZHluYW1pY190cmFpbih0cmFpbiA9IHRyYWluaW5nX3hnYltbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSB0ZXN0aW5nX3hnYltbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvb3N0ZXIgPSAiZ2J0cmVlIiwgIyBOb24tbGluw6lhaXJlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdW5kcyA9IDEsICMgVW4gc2V1bCBhcmJyZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG51bV9wYXJhbGxlbF90cmVlcyA9IDEpDQogIHhnYi5kdW1wKG1vZGVsID0gdGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgIGZuYW1lID0gcGFzdGUwKGZpbGVfdGFnLCAieGdiX2R0XyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5qc29uIiksICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICB3aXRoX3N0YXRzID0gVFJVRSwgIyBFbnJlZ2lzdHJlbWVudCBkZXMgc3RhdGlzdGlxdWVzIHNpIG1vZMOobGUgZ2J0cmVlDQogICAgICAgICAgIGR1bXBfZm9ybWF0ID0gImpzb24iKSAjIER1bXAgYXUgZm9ybWF0IGpzb24sIHLDqS11dGlsaXNhYmxlDQogIGFjY3VyYWN5W2ksIDNdIDwtIDEgLSB0ZW1wX21vZGVsJGV2YWx1YXRpb25fbG9nW1syXV1bMV0gIyBSw6ljdXDDqXJhdGlvbiBkdSBtZWlsbGV1ciByw6lzdWx0YXQNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IG1vZMOobGUgZGUgUmFuZG9tIEZvcmVzdCAoeGdib29zdCkNCiAgdGVtcF9tb2RlbCA8LSB4Z2JfZHluYW1pY190cmFpbih0cmFpbiA9IHRyYWluaW5nX3hnYltbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSB0ZXN0aW5nX3hnYltbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvb3N0ZXIgPSAiZ2J0cmVlIiwgIyBOb24tbGluw6lhaXJlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdW5kcyA9IDEsICMgVW5lIHNldWxlIGl0w6lyYXRpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1fcGFyYWxsZWxfdHJlZXMgPSAyMDApICMgRGUgMjAwIGFyYnJlcw0KICB4Z2IuZHVtcChtb2RlbCA9IHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICBmbmFtZSA9IHBhc3RlMChmaWxlX3RhZywgInhnYl9yZl8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuanNvbiIpLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgd2l0aF9zdGF0cyA9IFRSVUUsICMgRW5yZWdpc3RyZW1lbnQgZGVzIHN0YXRpc3RpcXVlcyBzaSBtb2TDqGxlIGdidHJlZQ0KICAgICAgICAgICBkdW1wX2Zvcm1hdCA9ICJqc29uIikgIyBEdW1wIGF1IGZvcm1hdCBqc29uLCByw6ktdXRpbGlzYWJsZQ0KICBhY2N1cmFjeVtpLCA0XSA8LSAxIC0gdGVtcF9tb2RlbCRldmFsdWF0aW9uX2xvZ1tbMl1dICMgUsOpY3Vww6lyYXRpb24gZHUgbWVpbGxldXIgcsOpc3VsdGF0DQogIA0KICAjIEVudHJhaW5lbWVudCBkdSBtb2TDqGxlIGQnYXJicmUgZGUgZMOpY2lzaW9uIGJvb3N0w6kgYXZlYyBwcm90ZWN0aW9uIGNvbnRyZSBsJ292ZXJmaXR0aW5nICh4Z2Jvb3N0KQ0KICB0ZW1wX21vZGVsIDwtIHhnYl9keW5hbWljX3RyYWluKHRyYWluID0gdHJhaW5pbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IHRlc3RpbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9vc3RlciA9ICJnYnRyZWUiLCAjIE5vbi1saW7DqWFpcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBucm91bmRzID0gMTAwMDAwMCwgIyBBcnLDqnTDqSBhdSBtZWlsbGV1ciByw6lzdWx0YXQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1fcGFyYWxsZWxfdHJlZXMgPSAxKQ0KICB4Z2IuZHVtcChtb2RlbCA9IHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICBmbmFtZSA9IHBhc3RlMChmaWxlX3RhZywgInhnYl9nYnRfIiwgc3ByaW50ZigiJTAyZCIsIGkpLCAiLmpzb24iKSwgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgIHdpdGhfc3RhdHMgPSBUUlVFLCAjIEVucmVnaXN0cmVtZW50IGRlcyBzdGF0aXN0aXF1ZXMgc2kgbW9kw6hsZSBnYnRyZWUNCiAgICAgICAgICAgZHVtcF9mb3JtYXQgPSAianNvbiIpICMgRHVtcCBhdSBmb3JtYXQganNvbiwgcsOpLXV0aWxpc2FibGUNCiAgYWNjdXJhY3lbaSwgNV0gPC0gMSAtIHRlbXBfbW9kZWwkZXZhbHVhdGlvbl9sb2dbWzJdXVt0ZW1wX21vZGVsJGJlc3RfaXRlcmF0aW9uXSAjIFLDqWN1cMOpcmF0aW9uIGR1IG1laWxsZXVyIHLDqXN1bHRhdA0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbW9kw6hsZSBkZSByw6lncmVzc2lvbiBsb2dpc3RpcXVlIChoMm8pDQogIHRlbXBfbW9kZWwgPC0gaDJvLmdsbSh5ID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluaW5nX2ZyYW1lID0gdHJhaW5pbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICB2YWxpZGF0aW9uX2ZyYW1lID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2lkID0gcGFzdGUwKCJoMm9fZ2xtXyIsIHNwcmludGYoIiUwMmQiLCBpKSksICMgTm9tIGR1IG1vZMOobGUNCiAgICAgICAgICAgICAgICAgICAgICAgIG1heF9pdGVyYXRpb25zID0gMTAwLCAjIDEwMCBpdMOpcmF0aW9ucyBkJ29wdGltaXNhdGlvbg0KICAgICAgICAgICAgICAgICAgICAgICAgc29sdmVyID0gIklSTFNNIiwgIyBTb2x2ZXVyIHBhciBkw6lmYXV0DQogICAgICAgICAgICAgICAgICAgICAgICBzdGFuZGFyZGl6ZSA9IEZBTFNFLCAjIFBhcyBkZSBzdGFuZGFyZGlzYXRpb24gcHVpc3F1ZSBbLTEsIDFdDQogICAgICAgICAgICAgICAgICAgICAgICBmYW1pbHkgPSAibXVsdGlub21pYWwiLCAjIENsYXNzaWZpY2F0aW9uIG11bHRpLWNsYXNzZQ0KICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDAsICMgUmVwcm9kdWN0aW9uIGRlcyByw6lzdWx0YXRzDQogICAgICAgICAgICAgICAgICAgICAgICBpbnRlcmNlcHQgPSBUUlVFKQ0KICBoMm8uZG93bmxvYWRfcG9qbyh0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgICAgICAgICAgcGF0aCA9IGZpbGVfaDJvLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgICAgICAgICAgZ2V0X2phciA9IEZBTFNFKSAjIFBhcyBkZSBmaWNoaWVyIC5qYXINCiAgYWNjdXJhY3lbaSwgNl0gPC0gdGVtcF9tb2RlbEBtb2RlbCR2YWxpZGF0aW9uX21ldHJpY3NAbWV0cmljcyRoaXRfcmF0aW9fdGFibGVbMSwgMl0NCiAgDQogICMgRW50cmFpbmVtZW50IGR1IG1vZMOobGUgZCdhcmJyZSBkZSBkw6ljaXNpb24gKGgybykNCiAgdGVtcF9tb2RlbCA8LSBoMm8ucmFuZG9tRm9yZXN0KHkgPSAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbmluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbGlkYXRpb25fZnJhbWUgPSB0ZXN0aW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgPSBwYXN0ZTAoImgyb19kdF8iLCBzcHJpbnRmKCIlMDJkIiwgaSkpLCAjIE5vbSBkdSBtb2TDqGxlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVfcmF0ZSA9IDEsICMgVG91dGVzIGxlcyBvYnNlcnZhdGlvbnMgc2Vyb250IHByaXNlcyBlbiBjb21wdGUgcG91ciBsZSBzZXVsIGFyYnJlIGRlIGTDqWNpc2lvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXRyaWVzID0gc3VtKGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkZGlzY3JldGUpLCAjIFRvdXRlcyBsZXMgZmVhdHVyZXMgc2Vyb250IHByaXNlcyBlbiBjb21wdGUgcG91ciBsZSBzZXVsIGFyYnJlIGRlIGTDqWNpc2lvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnRyZWVzID0gMSwgIyBVbiBzZXVsIGFyYnJlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWVkID0gMCkgIyBSZXByb2R1Y3Rpb24gZGVzIHLDqXN1bHRhdHMNCiAgaDJvLmRvd25sb2FkX3Bvam8odGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgICAgICAgICAgIHBhdGggPSBmaWxlX2gybywgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgICAgICAgICAgIGdldF9qYXIgPSBGQUxTRSkgIyBQYXMgZGUgZmljaGllciAuamFyDQogIGFjY3VyYWN5W2ksIDddIDwtIDEgLSBtaW4odGVtcF9tb2RlbEBtb2RlbCRzY29yaW5nX2hpc3RvcnkkdmFsaWRhdGlvbl9jbGFzc2lmaWNhdGlvbl9lcnJvciwgbmEucm0gPSBUUlVFKQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbW9kw6hsZSBkZSBSYW5kb20gRm9yZXN0IChoMm8pDQogIHRlbXBfbW9kZWwgPC0gaDJvLnJhbmRvbUZvcmVzdCh5ID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluaW5nX2ZyYW1lID0gdHJhaW5pbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWxpZGF0aW9uX2ZyYW1lID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2lkID0gcGFzdGUwKCJoMm9fcmZfIiwgc3ByaW50ZigiJTAyZCIsIGkpKSwgIyBOb20gZHUgbW9kw6hsZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3JhdGUgPSAwLjYzMiwgIyBCb290c3RyYXBwaW5nIC42MzIgcG91ciBjaGFxdWUgYXJicmUgZGUgZMOpY2lzaW9uDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJpZXMgPSAtMSwgIyBzcXJ0KDM2KSBmZWF0dXJlcyBzZXJvbnQgcHJpc2VzIGVuIGNvbXB0ZSBwb3VyIGNoYXF1ZSBhcmJyZSBkZSBkw6ljaXNpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlcyA9IDIwMCwgIyAyMDAgYXJicmVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWVkID0gMCkgIyBSZXByb2R1Y3Rpb24gZGVzIHLDqXN1bHRhdHMNCiAgaDJvLmRvd25sb2FkX3Bvam8odGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgICAgICAgICAgIHBhdGggPSBmaWxlX2gybywgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgICAgICAgICAgIGdldF9qYXIgPSBGQUxTRSkgIyBQYXMgZGUgZmljaGllciAuamFyDQogIGFjY3VyYWN5W2ksIDhdIDwtIDEgLSBtaW4odGVtcF9tb2RlbEBtb2RlbCRzY29yaW5nX2hpc3RvcnkkdmFsaWRhdGlvbl9jbGFzc2lmaWNhdGlvbl9lcnJvciwgbmEucm0gPSBUUlVFKQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbW9kw6hsZSBkJ2FyYnJlIGRlIGTDqWNpc2lvbiBib29zdMOpIGF2ZWMgcHJvdGVjdGlvbiBjb250cmUgbCdvdmVyZml0dGluZyAoaDJvKQ0KICB0ZW1wX21vZGVsIDwtIGgyby5nYm0oeSA9IDEsDQogICAgICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbmluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICB2YWxpZGF0aW9uX2ZyYW1lID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9pZCA9IHBhc3RlMCgiaDJvX2didF8iLCBzcHJpbnRmKCIlMDJkIiwgaSkpLCAjIE5vbSBkdSBtb2TDqGxlDQogICAgICAgICAgICAgICAgICAgICAgZGlzdHJpYnV0aW9uID0gIm11bHRpbm9taWFsIiwgIyBDbGFzc2lmaWNhdGlvbiBtdWx0aS1jbGFzc2UNCiAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVfcmF0ZSA9IDEsICMgUGFzIGRlIHByb2Nlc3N1cyBzdG9jaGFzdGlxdWUNCiAgICAgICAgICAgICAgICAgICAgICBudHJlZXMgPSAxMDAsICMgMTAwIGl0w6lyYXRpb25zIGRlIGJvb3N0aW5nIGF1IG1heGltdW0NCiAgICAgICAgICAgICAgICAgICAgICBzY29yZV9lYWNoX2l0ZXJhdGlvbiA9IFRSVUUsICMgTm90ZXIgbGEgdmFsZXVyIGRlIGNoYXF1ZSBpdMOpcmF0aW9uDQogICAgICAgICAgICAgICAgICAgICAgc3RvcHBpbmdfcm91bmRzID0gMTAsICMgQXJyw6p0IGFwcsOocyAxMCBpdMOpcmF0aW9ucyBzYW5zIGFtw6lsaW9yYXRvbiBkZSBsYSBtw6l0cmlxdWUNCiAgICAgICAgICAgICAgICAgICAgICBzdG9wcGluZ19tZXRyaWMgPSAibWlzY2xhc3NpZmljYXRpb24iLCAjIFN1cnZlaWxsZXIgbCdpbmV4YWN0aXR1ZGUgZGUgbGEgY2xhc3NpZmljYXRpb24gcG91ciBsJ2FycsOqdA0KICAgICAgICAgICAgICAgICAgICAgIHN0b3BwaW5nX3RvbGVyYW5jZSA9IDAuMDAwMDEsICMgQXJyw6p0ZXIgbG9yc3F1ZSBsYSBtw6l0cmlxdWUgc3RhZ25lIGRlIDAuMDAxJQ0KICAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSAwKSAjIFJlcHJvZHVjdGlvbiBkZXMgcsOpc3VsdGF0cw0KICBoMm8uZG93bmxvYWRfcG9qbyh0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgICAgICAgICAgcGF0aCA9IGZpbGVfaDJvLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgICAgICAgICAgZ2V0X2phciA9IEZBTFNFKSAjIFBhcyBkZSBmaWNoaWVyIC5qYXINCiAgYWNjdXJhY3lbaSwgOV0gPC0gMSAtIG1pbih0ZW1wX21vZGVsQG1vZGVsJHNjb3JpbmdfaGlzdG9yeSR2YWxpZGF0aW9uX2NsYXNzaWZpY2F0aW9uX2Vycm9yLCBuYS5ybSA9IFRSVUUpDQogIA0KICAjIEVudHJhaW5lbWVudCBkdSByw6lzZWF1IGRlIG5ldXJvbmVzIMOgIGFyY2hpdGVjdHVyZSAzMng2ICsgUmVMVSAoaDJvKQ0KICB0ZW1wX21vZGVsIDwtIGgyb19ubl90cmFpbih0cmFpbiA9IHRyYWluaW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgPSBwYXN0ZTAoImgyb19ubl8zMng2X1JlTFVfIiwgc3ByaW50ZigiJTAyZCIsIGkpKSwgIyBOb20gZHUgbW9kw6hsZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhY3RpdmF0aW9uID0gIlJlY3RpZmllciIsICMgUmVMVQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoaWRkZW4gPSAzMikgIyBBcmNoaXRlY3R1cmUgMzJ4Ng0KICBoMm8uZG93bmxvYWRfcG9qbyh0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgICAgICAgICAgcGF0aCA9IGZpbGVfaDJvLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgICAgICAgICAgZ2V0X2phciA9IEZBTFNFKSAjIFBhcyBkZSBmaWNoaWVyIC5qYXINCiAgYWNjdXJhY3lbaSwgMTBdIDwtIDEgLSBtaW4odGVtcF9tb2RlbEBtb2RlbCRzY29yaW5nX2hpc3RvcnkkdmFsaWRhdGlvbl9jbGFzc2lmaWNhdGlvbl9lcnJvciwgbmEucm0gPSBUUlVFKQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgcsOpc2VhdSBkZSBuZXVyb25lcyDDoCBhcmNoaXRlY3R1cmUgMzJ4NiArIFRhbmggKGgybykNCiAgdGVtcF9tb2RlbCA8LSBoMm9fbm5fdHJhaW4odHJhaW4gPSB0cmFpbmluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IHRlc3RpbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2lkID0gcGFzdGUwKCJoMm9fbm5fMzJ4Nl9UYW5oXyIsIHNwcmludGYoIiUwMmQiLCBpKSksICMgTm9tIGR1IG1vZMOobGUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWN0aXZhdGlvbiA9ICJUYW5oIiwgIyAiU2lnbW9pZGUiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhpZGRlbiA9IDMyKSAjIEFyY2hpdGVjdHVyZSAzMng2DQogIGgyby5kb3dubG9hZF9wb2pvKHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICAgICAgICAgICBwYXRoID0gZmlsZV9oMm8sICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICAgICAgICAgICBnZXRfamFyID0gRkFMU0UpICMgUGFzIGRlIGZpY2hpZXIgLmphcg0KICBhY2N1cmFjeVtpLCAxMV0gPC0gMSAtIG1pbih0ZW1wX21vZGVsQG1vZGVsJHNjb3JpbmdfaGlzdG9yeSR2YWxpZGF0aW9uX2NsYXNzaWZpY2F0aW9uX2Vycm9yLCBuYS5ybSA9IFRSVUUpDQogIA0KICAjIEVudHJhaW5lbWVudCBkdSByw6lzZWF1IGRlIG5ldXJvbmVzIMOgIGFyY2hpdGVjdHVyZSAxNngxNng2ICsgUmVMVSAoaDJvKQ0KICB0ZW1wX21vZGVsIDwtIGgyb19ubl90cmFpbih0cmFpbiA9IHRyYWluaW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgPSBwYXN0ZTAoImgyb19ubl8xNngxNng2X1JlTFVfIiwgc3ByaW50ZigiJTAyZCIsIGkpKSwgIyBOb20gZHUgbW9kw6hsZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhY3RpdmF0aW9uID0gIlJlY3RpZmllciIsICMgUmVMVQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoaWRkZW4gPSBjKDE2LCAxNikpICMgQXJjaGl0ZWN0dXJlIDE2eDE2eDYNCiAgaDJvLmRvd25sb2FkX3Bvam8odGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgICAgICAgICAgIHBhdGggPSBmaWxlX2gybywgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgICAgICAgICAgIGdldF9qYXIgPSBGQUxTRSkgIyBQYXMgZGUgZmljaGllciAuamFyDQogIGFjY3VyYWN5W2ksIDEyXSA8LSAxIC0gbWluKHRlbXBfbW9kZWxAbW9kZWwkc2NvcmluZ19oaXN0b3J5JHZhbGlkYXRpb25fY2xhc3NpZmljYXRpb25fZXJyb3IsIG5hLnJtID0gVFJVRSkNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IHLDqXNlYXUgZGUgbmV1cm9uZXMgw6AgYXJjaGl0ZWN0dXJlIDE2eDE2eDYgKyBUYW5oIChoMm8pDQogIHRlbXBfbW9kZWwgPC0gaDJvX25uX3RyYWluKHRyYWluID0gdHJhaW5pbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSB0ZXN0aW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9pZCA9IHBhc3RlMCgiaDJvX25uXzE2eDE2eDZfVGFuaF8iLCBzcHJpbnRmKCIlMDJkIiwgaSkpLCAjIE5vbSBkdSBtb2TDqGxlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFjdGl2YXRpb24gPSAiVGFuaCIsICMgIlNpZ21vaWRlIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoaWRkZW4gPSBjKDE2LCAxNikpICMgQXJjaGl0ZWN0dXJlIDE2eDE2eDYNCiAgaDJvLmRvd25sb2FkX3Bvam8odGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgICAgICAgICAgIHBhdGggPSBmaWxlX2gybywgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgICAgICAgICAgIGdldF9qYXIgPSBGQUxTRSkgIyBQYXMgZGUgZmljaGllciAuamFyDQogIGFjY3VyYWN5W2ksIDEzXSA8LSAxIC0gbWluKHRlbXBfbW9kZWxAbW9kZWwkc2NvcmluZ19oaXN0b3J5JHZhbGlkYXRpb25fY2xhc3NpZmljYXRpb25fZXJyb3IsIG5hLnJtID0gVFJVRSkNCiAgDQp9DQoNCiMgVGVtcHMgbsOpY2Vzc2FpcmUNCnRpbWluZyhDdXJyZW50VGltZSwgIkNyw6lhdGlvbiBldCDDqXZhbHVhdGlvbiBkZXMgZG91emUgbW9kw6hsZXMgYXZlYyBmZWF0dXJlcyBzw6lsZWN0aW9ubsOpZXMiKQ0KYGBgDQoNCiMjIEFmZmljaGFnZSBkZXMgcsOpc3VsdGF0cw0KDQpMZXMgbW9kw6hsZXMgc29udCBiaWVuIHBsdXMgcGVyZm9ybWFudHMgcXUnaW5pdGlhbGVtZW50IGFwcsOocyB1bmUgc8OpbGVjdGlvbiBkZXMgZmVhdHVyZXMuIE9uIGF0dGVpbnQgZMOpc29ybWFpcyB1bmUgbW95ZW5uZSBoYXV0ZSBkJ2V4YWN0aXR1ZGUgZGUgNjglLCBjb250cmUgNjMlIGF1cGFyYXZhbnQuIEMnZXN0IHVuIGF2YW5jZW1lbnQgaW1wb3J0YW50IGRhbnMgbGEgcGVyZm9ybWFuY2UgZGVzIG1vZMOobGVzLCBldCBvbiBzYWl0IHF1J29uIHBldXQgZW5jb3JlIGFqb3V0ZXIgMy00JSBkZSBwZXJmb3JtYW5jZSBhdXggbW9kw6hsZXMgbGVzIHBsdXMgcGVyZm9ybWFudHMgYXZlYyB1biB0dW5pbmcgZGVzIGh5cGVycGFyYW3DqHRyZXMuDQoNCk9uIHJlbWFycXVlIGxlcyBtw6ptZXMgbW9kw6hsZXMgc29udCB0b3Vqb3VycyBsZXMgcGx1cyBwZXJmb3JtYW50cyA6IGNlIHNvbnQgbGVzIG1vZMOobGVzIGxpbsOpYWlyZXMgIQ0KDQpgYGB7ciBSZXN1bHRhdHM0fQ0KIyBNb3llbm5lIGRlcyByw6lzdWx0YXRzDQpmb3IgKGkgaW4gMjoxMykgew0KICBhY2N1cmFjeVsxMywgaV0gPC0gbWVhbihhY2N1cmFjeVsxOjYsIGldKQ0KICBhY2N1cmFjeVsxNCwgaV0gPC0gbWVhbihhY2N1cmFjeVs3OjksIGldKQ0KICBhY2N1cmFjeVsxNSwgaV0gPC0gbWVhbihhY2N1cmFjeVsxMDoxMiwgaV0pDQogIGFjY3VyYWN5WzE2LCBpXSA8LSBtZWFuKGFjY3VyYWN5WzEzOjE1LCBpXSkNCn0NCg0KIyBFbnJlZ2lzdHJlbWVudCBkZXMgc2NvcmVzDQpmd3JpdGUoYWNjdXJhY3ksICJzY29yZXMvNF9tb2RlbHMuY3N2IikNCg0KIyBBZmZpY2hhZ2UgZGVzIHLDqXN1bHRhdHMgZGFucyB1biB0YWJsZWF1IGludGVyYWN0aWYNCnRvX3ByaW50IDwtIGRhdGEudGFibGUodChhY2N1cmFjeVsxMzoxNiwgLTFdKSkgIyBQcsOpcGFyYXRpb24gZGVzIGRvbm7DqWVzIMOgIG1ldHRyZSBzdXIgdGFibGUNCmNvbG5hbWVzKHRvX3ByaW50KSA8LSBjKCIxIGNvbnRyZSAxIiwgIjEgY29udHJlIDIiLCAiMiBjb250cmUgMSIsICJNb3llbm5lIikgIyBSZW1pc2UgZGVzIG5vbXMgZGVzIGNvbG9ubmVzDQpyb3cubmFtZXModG9fcHJpbnQpIDwtIGNvbG5hbWVzKGFjY3VyYWN5KVstMV0gIyBSZW1pc2UgZGVzIG5vbXMgZGVzIGxpZ25lcw0KZGF0YXRhYmxlKHRvX3ByaW50LA0KICAgICAgICAgIGZpbHRlciA9ICJ0b3AiLCAjIEZpbHRyYWdlIGF1LWRlc3N1cyBkZSBsYSB0YWJsZQ0KICAgICAgICAgIGNsYXNzID0gImNlbGwtYm9yZGVyIHN0cmlwZSIsICMgQ1NTDQogICAgICAgICAgZXh0ZW5zaW9ucyA9IGMoIkNvbFJlb3JkZXIiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICJSb3dSZW9yZGVyIiksICMgUmVvcmRvbm5lciBtYW51ZWxsZW1lbnQgw6AgbGEgbWFpbg0KICAgICAgICAgIG9wdGlvbnMgPSBsaXN0KHBhZ2VMZW5ndGggPSAxMiwgIyBQYWdlIGFmZmljaGFudCAxMiBsaWduZXMNCiAgICAgICAgICAgICAgICAgICAgICAgICBvcmRlciA9IGxpc3QobGlzdCg0LCAiZGVzYyIpKSwgIyBPcmRvbm5lciBwYXIgZMOpZmF1dCBwYXIgbCdleGFjdGl0dWRlIG1veWVubmUNCiAgICAgICAgICAgICAgICAgICAgICAgICBjb2xSZW9yZGVyID0gVFJVRSwgIyBQbHVnaW4NCiAgICAgICAgICAgICAgICAgICAgICAgICByb3dSZW9yZGVyID0gVFJVRSkpICU+JSAjIFBsdWdpbg0KICBmb3JtYXRTdHlsZShjKCIxIGNvbnRyZSAxIiwgIjEgY29udHJlIDIiLCAiMiBjb250cmUgMSIpLA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IHN0eWxlQ29sb3JCYXIoYygwLCAxKSwgJ2xpZ2h0Z3JlZW4nKSwgIyBDb3VsZXVyIHZlcnQgY2xhaXIgcG91ciBsZXMgbcOpdHJpcXVlcyBwYXIgZm9sZA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZFNpemUgPSAnMTAwJSA5MCUnLA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZFJlcGVhdCA9ICduby1yZXBlYXQnLA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZFBvc2l0aW9uID0gJ2NlbnRlcicpICU+JQ0KICBmb3JtYXRTdHlsZSgiTW95ZW5uZSIsDQogICAgICAgICAgICAgIGJhY2tncm91bmQgPSBzdHlsZUNvbG9yQmFyKGMoMCwgMSksICdwaW5rJyksICMgQ291bGV1ciByb3NlIHBvdXIgbGEgbcOpdHJpcXVlIGRlIG1veWVubmUNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFNpemUgPSAnMTAwJSA5MCUnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgIGJhY2tncm91bmRQb3NpdGlvbiA9ICdjZW50ZXInKSAlPiUNCiAgZm9ybWF0UGVyY2VudGFnZShjb2x1bW5zID0gYygiMSBjb250cmUgMSIsICIxIGNvbnRyZSAyIiwgIjIgY29udHJlIDEiKSwNCiAgICAgICAgICAgICAgZGlnaXRzID0gOCkgJT4lDQogIGZvcm1hdFBlcmNlbnRhZ2UoY29sdW1ucyA9ICJNb3llbm5lIiwNCiAgICAgICAgICAgICAgZGlnaXRzID0gOCkNCg0KIyBBZmZpY2hhZ2UgZGVzIHLDqXN1bHRhdHMgZGFucyB1biB0YWJsZWF1IHN0YXRpcXVlDQpmb3JtYXR0YWJsZShhY2N1cmFjeVssIGMoMSwgMjo1KV0sIGxpc3QoZm9ybWF0dGFibGU6OmFyZWEoY29sID0geGdiX0xpbmVhck1vZGVsOnhnYl9HcmFkaWVudEJvb3N0aW5nKSB+IGNvbG9yX2Jhcigib3JhbmdlIikpKQ0KZm9ybWF0dGFibGUoYWNjdXJhY3lbLCBjKDEsIDY6OSldLCBsaXN0KGZvcm1hdHRhYmxlOjphcmVhKGNvbCA9IGgyb19MaW5lYXJNb2RlbDpoMm9fR3JhZGllbnRCb29zdGluZykgfiBjb2xvcl9iYXIoImN5YW4iKSkpDQpmb3JtYXR0YWJsZShhY2N1cmFjeVssIGMoMSwgMTA6MTMpXSwgbGlzdChmb3JtYXR0YWJsZTo6YXJlYShjb2wgPSBoMm9fTk5fMzJ4Nl9SZUxVOmgyb19OTl8xNngxNng2X1NvZnQpIH4gY29sb3JfYmFyKCJ5ZWxsb3ciKSkpDQpgYGANCg0KIyMgQW5hbHlzZSBkdSBtb2TDqGxlIGRlIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUNCg0KTG9yc3F1J29uIHV0aWxpc2UgbGVzIG1laWxsZXVycyBwYXJhbcOodHJlcyBwb3VyIGxlIG1vZMOobGUgZGUgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSwgb24gb2J0aWVudCBsZXMgcsOpc3VsdGF0cyBjaS1kZXNzb3VzLg0KDQpMYSBzYWxsZSAxIHNlbWJsZSB0b3Vqb3VycyBjYXVzZXIgZGVzIHByb2Jsw6htZXMsIGlsIGVzdCBwb3NzaWJsZSBxdSdpbCB5IGEgdG91am91cnMgdW4gbWF1dmFpcyBjb25kaXRpb25uZW1lbnQgZGVzIGRvbm7DqWVzIGlzc3VlcyBkZSBjZXR0ZSBzYWxsZS4gTGEgc2FsbGUgMiBldCAzIHNlbWJsZW50IGxlcyBwbHVzIHN0YWJsZXMsIGV0IHNlbWJsZSBpbmZvcm1lciBxdWUgOg0KDQotIEwndXRpbGlzYXRpb24gZGVzIDQgYW5jcmVzIHBlcm1ldCBkJ29idGVuaXIgbGUgcGx1cyBkJ2luZm9ybWF0aW9uLCBjZSBxdWkgYSDDqXTDqSBlbXBpcmlxdWVtZW50IHbDqXJpZmnDqSBwYXIgQmFjY2l1IGV0IGFsLiBkYW5zICpBbiBleHBlcmltZW50YWwgY2hhcmFjdGVyaXphdGlvbiBvZiByZXNlcnZvaXIgY29tcHV0aW5nIGluIGFtYmllbnQgYXNzaXN0ZWQgbGl2aW5nIGFwcGxpY2F0aW9ucyosIGV0IHbDqXJpZmnDqSDDqWdhbGVtZW50IGljaSAocHLDqXNlbmNlIGRlIHRvdXRlcyBsZXMgYW5jcmVzIGRhbnMgbGVzIGZlYXR1cmVzIHPDqWxlY3Rpb25uw6llcykNCi0gTGVzIGZlYXR1cmVzIHBldXZlbnQgw6p0cmUgZW4gY29udHJhZGljdGlvbiBzZWxvbiBsZXMgc2FsbGVzIChwYXIgZXhlbXBsZSwgUmVzaTFfMSBzZW1ibGUgaW51dGlsZSBwb3VyIGxlIGxhYmVsIDMgZGUgbGEgc2FsbGUgMiBhdmVjIHVuIGNvZWZmaWNpZW50IHF1YXNpbWVudCDDoCAwLCBtYWlzIGludMOpZ3JhbGVtZW50IHV0aWxlIHBvdXIgbGUgbcOqbWUgbGFiZWwgcG91ciBsYSBzYWxsZSAzKQ0KLSBMJ2VudHJhaW5lbWVudCBkZSBsYSBzYWxsZSAxIGVzdCBzaSByYXBpZGUgcXVlIGwnb3ZlcmZpdHRpbmcgYXBwYXJhaXQgaW1tw6lkaWF0ZW1lbnQgc2kgbGUgbm9tYnJlIGQnaXTDqXJhdGlvbnMgYXVnbWVudGUgw6AgdW4gbm9tYnJlIHN1cMOpcmlldXIgw6AgMSBjaGlmZnJlLg0KDQpJbCBlc3QgY2xhaXIgw6lnYWxlbWVudCBxdWUgbGEgY2hlbWluIDMgYSDDqXTDqSBlbnRpw6hyZW1lbnQgcGVyZHVlIGVuIGZhdmV1ciBkZSB0b3V0ZXMgbGVzIGF1dHJlcyBzYWxsZXMuIENlbGEgZXN0IHVuZSBib25uZSBldCB1bmUgbWF1dmFpc2UgY2hvc2UsIGNhciBsZSBtb2TDqGxlIHNlbWJsZSBwcml2aWzDqWdpZXIgbGEgcHLDqWRpY3Rpb24gZGUgbGEgY2hlbWluIDEgZW4gdG91dCBwb2ludCAoMzUlIGRlcyBwcsOpZGljdGlvbnMgcG91ciB1bmlxdWVtZW50IDI1JSBkZXMgdmFsZXVycykuIElsIGVzdCB0b3V0IMOgIGZhaXQgcGxhdXNpYmxlIHF1ZSBsZSBjaGVtaW4gMyBzb2l0IGRpZmZpY2lsZSDDoCBwcsOpZGlyZSBldCBxdWUgbCdvcHRpbWlzYXRpb24gYSBwcml2aWzDqWdpw6kgbGVzIGF1dHJlcyBjaGVtaW5zLCBwdWlzcXUnaWxzIHBvdXJyYWllbnQgw6p0cmUgcGx1cyBmYWNpbGUgw6Agb3B0aW1pc2VyIGxpbsOpYWlyZW1lbnQuDQoNCmBgYHtyIEFuYWx5c2VMb2dpc3RpcXVlNCwgd2FybmluZ3MgPSBGQUxTRX0NCiMgQ29tcHRldXIgZGUgdGVtcHMNCkN1cnJlbnRUaW1lIDwtIHRpbWVyKCkgIyBDaHVuayBQcsOpcGFyYXRpb24gZGUgbCdhbmFseXNlIGR1IGRlcm5pZXIgbW9kw6hsZSBkZSByw6lncmVzc2lvbiBsb2dpc3RpcXVlDQoNCiMgUHLDqS1pbml0aWFsaXNhdGlvbiBkZXMgdmFyaWFibGVzDQpwcmVkaWN0ZWRWYWx1ZXMgPC0gbWF0cml4KG5yb3cgPSAzMTQsIG5jb2wgPSA2KQ0KZXZvbHV0aW9uIDwtIGxpc3QoKQ0KdGVtcF9kdCA8LSBsaXN0KCkNCnRlbXBfbWVhbnMgPC0gZGF0YS5mcmFtZShGZWF0dXJlID0gYyhwYXN0ZTAocmVwKGMocGFzdGUwKCJDb2VmIiwgMTo0KSwgcGFzdGUwKCJSw6lzaSIsIDE6NCkpLCA0KSwgcGFzdGUwKCJfIiwgaW52ZXJzZS5ybGUobGlzdChsZW5ndGhzID0gcmVwKDgsIDQpLCB2YWx1ZXMgPSAxOjQpKSkpLCBwYXN0ZTAoIlBvc0luaXRpYWxlXyIsIDE6NCkpW3doaWNoKGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkZGlzY3JldGUgPT0gMSldLA0KICAgICAgICAgICAgICAgICAgICAgICAgIEZvbGRfMSA9IG51bWVyaWMoc3VtKGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkZGlzY3JldGUgPT0gMSkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIEZvbGRfMiA9IG51bWVyaWMoc3VtKGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkZGlzY3JldGUgPT0gMSkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIEZvbGRfMyA9IG51bWVyaWMoc3VtKGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkZGlzY3JldGUgPT0gMSkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIEZvbGRfTWVhbiA9IG51bWVyaWMoc3VtKGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkZGlzY3JldGUgPT0gMSkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIEZlYXR1cmVfTWVhbiA9IG51bWVyaWMoc3VtKGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkZGlzY3JldGUgPT0gMSkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIEZlYXR1cmVfU0QgPSBudW1lcmljKHN1bShiZXN0X3dlaWdodHMkb3B0aW1pemVyJGRpc2NyZXRlID09IDEpKSkNCg0KIyBCb3VjbGUgZCdlbnRyYWluZW1lbnQgMiBjb250cmUgMQ0KZm9yIChpIGluIDEwOjEyKSB7DQogIA0KICAjIEVudHJhaW5lbWVudCBkJ3VuIG1vZMOobGUgbGluw6lhaXJlDQogIHRlbXBfbW9kZWwgPC0geGdiLnRyYWluKGRhdGEgPSB0cmFpbmluZ194Z2JbW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgd2F0Y2hsaXN0ID0gbGlzdCh0ZXN0ID0gdGVzdGluZ194Z2JbW2ldXSksICMgRG9ubsOpZXMgZGUgdmFsaWRhdGlvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICBudW1fY2xhc3MgPSA2LCAjIDYgY2xhc3NlIGRlIGNsYXNzaWZpY2F0aW9uDQogICAgICAgICAgICAgICAgICAgICAgICAgIG50aHJlYWQgPSAxLCAjIDEgdGhyZWFkIHBvdXIgbGEgcmVwcm9kdWN0aW9uIGRlcyByw6lzdWx0YXRzDQogICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3VuZHMgPSAxMDAwLCAjIDEwMDAgaXTDqXJhdGlvbnMsIG/DuSBhcnLDqnQgcHLDqW1hdHVyw6kNCiAgICAgICAgICAgICAgICAgICAgICAgICAgYWxwaGEgPSBiZXN0X3dlaWdodHMkb3B0aW1pemVyJGNvbnRpbnVvdXNbMV0sICMgUsOpZ3VsYXJpc2F0aW9uIEwxIChMYXNzbykNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbGFtYmRhID0gYmVzdF93ZWlnaHRzJG9wdGltaXplciRjb250aW51b3VzWzJdLCAjIFLDqWd1bGFyaXNhdGlvbiBMMiAoUmlkZ2UpDQogICAgICAgICAgICAgICAgICAgICAgICAgIGxhbWJkYV9iaWFzID0gYmVzdF93ZWlnaHRzJG9wdGltaXplciRjb250aW51b3VzWzNdLCAjIFLDqWd1bGFyaXNhdGlvbiBMMiBkdSBiaWFpcyAoUmlkZ2UpDQogICAgICAgICAgICAgICAgICAgICAgICAgIGV0YSA9IDAuMTAsICMgU2hyaW5rYWdlIHBvdXIgbGUgYm9vc3RpbmcNCiAgICAgICAgICAgICAgICAgICAgICAgICAgYm9vc3RlciA9ICJnYmxpbmVhciIsICMgVHlwZSBkJ2VudHJhaW5lbWVudCA6IGxpbsOpYWlyZSBvdSBub24tbGluw6lhaXJlDQogICAgICAgICAgICAgICAgICAgICAgICAgIG9iamVjdGl2ZSA9ICJtdWx0aTpzb2Z0cHJvYiIsICMgR3JhZGllbnQvSGVzc2lhbiBwb3VyIGwnb3B0aW1pc2F0aW9uIHBhciBHcmFkaWVudCBEZXNjZW50DQogICAgICAgICAgICAgICAgICAgICAgICAgIGV2YWxfbWV0cmljID0gIm1lcnJvciIsICMgSW5leGFjdGl0dWRlIGRlIGxhIGNsYXNzaWZpY2F0aW9uIChjZXR0ZSBtw6l0cmlxdWUgZXN0IG9wdGltaXPDqWUgcGFyIHhnYm9vc3QpDQogICAgICAgICAgICAgICAgICAgICAgICAgIG1heGltaXplID0gRkFMU0UsICMgTWluaW1pc2F0aW9uIGRlIGwnZXJyZXVyDQogICAgICAgICAgICAgICAgICAgICAgICAgIGVhcmx5X3N0b3BwaW5nX3JvdW5kcyA9IDUwLCAjIEFycsOqdCBhcHLDqHMgNTAgaXTDqXJhdGlvbnMgc2FucyBhbcOpbGlvcmF0aW9uIGRlIGxhIG3DqXRyaXF1ZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICB2ZXJib3NlID0gRkFMU0UsICMgU2FucyBwcmludCBkZXMgaXTDqXJhdGlvbnMNCiAgICAgICAgICAgICAgICAgICAgICAgICAgY2FsbGJhY2tzID0gbGlzdChjYi5ldmFsdWF0aW9uLmxvZygpKSkgIyBMb2dnaW5nIGRlcyBkb25uw6llcyBkJ2VudHJhaW5lbWVudCBwb3VyIHBvdXZvaXIgcsOpY3Vww6lyZXIgbGVzIG3DqXRyaXF1ZXMpDQogIA0KICAjIEVucmVnaXN0cmVtZW50IGR1IGxvZw0KICBldm9sdXRpb25bW2kgLSA5XV0gPC0gY2JpbmQodGVtcF9tb2RlbCRldmFsdWF0aW9uX2xvZywgRm9sZCA9IHJlcCgxMyAtIGksIHRlbXBfbW9kZWwkbml0ZXIpKQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbWVpbGxldXIgbW9kw6hsZSAob2J0ZW50aW9uIGRlcyBtZWlsbGV1cnMgY29lZmZpY2llbnRzKQ0KICB0ZW1wX21vZGVsIDwtIHhnYi50cmFpbihkYXRhID0gdHJhaW5pbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgIHdhdGNobGlzdCA9IGxpc3QodGVzdCA9IHRlc3RpbmdfeGdiW1tpXV0pLCAjIERvbm7DqWVzIGRlIHZhbGlkYXRpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtX2NsYXNzID0gNiwgIyA2IGNsYXNzZSBkZSBjbGFzc2lmaWNhdGlvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICBudGhyZWFkID0gMSwgIyAxIHRocmVhZCBwb3VyIGxhIHJlcHJvZHVjdGlvbiBkZXMgcsOpc3VsdGF0cw0KICAgICAgICAgICAgICAgICAgICAgICAgICBucm91bmRzID0gdGVtcF9tb2RlbCRiZXN0X2l0ZXJhdGlvbiwgIyBNZWlsbGV1cmUgaXTDqXJhdGlvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkY29udGludW91c1sxXSwgIyBSw6lndWxhcmlzYXRpb24gTDEgKExhc3NvKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICBsYW1iZGEgPSBiZXN0X3dlaWdodHMkb3B0aW1pemVyJGNvbnRpbnVvdXNbMl0sICMgUsOpZ3VsYXJpc2F0aW9uIEwyIChSaWRnZSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbGFtYmRhX2JpYXMgPSBiZXN0X3dlaWdodHMkb3B0aW1pemVyJGNvbnRpbnVvdXNbM10sICMgUsOpZ3VsYXJpc2F0aW9uIEwyIGR1IGJpYWlzIChSaWRnZSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZXRhID0gMC4xMCwgIyBTaHJpbmthZ2UgcG91ciBsZSBib29zdGluZw0KICAgICAgICAgICAgICAgICAgICAgICAgICBib29zdGVyID0gImdibGluZWFyIiwgIyBUeXBlIGQnZW50cmFpbmVtZW50IDogbGluw6lhaXJlIG91IG5vbi1saW7DqWFpcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gIm11bHRpOnNvZnRwcm9iIiwgIyBHcmFkaWVudC9IZXNzaWFuIHBvdXIgbCdvcHRpbWlzYXRpb24gcGFyIEdyYWRpZW50IERlc2NlbnQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZXZhbF9tZXRyaWMgPSAibWVycm9yIiwgIyBJbmV4YWN0aXR1ZGUgZGUgbGEgY2xhc3NpZmljYXRpb24gKGNldHRlIG3DqXRyaXF1ZSBlc3Qgb3B0aW1pc8OpZSBwYXIgeGdib29zdCkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4aW1pemUgPSBGQUxTRSwgIyBNaW5pbWlzYXRpb24gZGUgbCdlcnJldXINCiAgICAgICAgICAgICAgICAgICAgICAgICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gOTk5OTksICMgU2FucyBhcnLDqnQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZSA9IEZBTFNFLCAjIFNhbnMgcHJpbnQgZGVzIGl0w6lyYXRpb25zDQogICAgICAgICAgICAgICAgICAgICAgICAgIGNhbGxiYWNrcyA9IGxpc3QoY2IuZXZhbHVhdGlvbi5sb2coKSkpICMgTG9nZ2luZyBkZXMgZG9ubsOpZXMgZCdlbnRyYWluZW1lbnQgcG91ciBwb3V2b2lyIHLDqWN1cMOpcmVyIGxlcyBtw6l0cmlxdWVzKQ0KICANCiAgIyBQcsOpZGljdGlvbiBkdSBtb2TDqGxlIGxpbsOpYWlyZQ0KICBwcmVkaWN0ZWRWYWx1ZXNbZm9sZHNfdGVzdFtbaV1dLCBdIDwtIHQobWF0cml4KHByZWRpY3QodGVtcF9tb2RlbCwgdGVzdGluZ194Z2JbW2ldXSwgbnRyZWVsaW1pdCA9IDApLCBucm93ID0gNikpDQogIA0KICAjIENhbGN1bCBldCBmb3JtYXR0YWdlIGRlIGwnaW1wb3J0YW5jZSBkZXMgdmFyaWFibGVzDQogIHRlbXBfaW1wb3J0YW5jZSA8LSBkYXRhLnRhYmxlKEZlYXR1cmUgPSB0ZW1wX21lYW5zW1siRmVhdHVyZSJdXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF0cml4KHhnYi5pbXBvcnRhbmNlKG1vZGVsID0gdGVtcF9tb2RlbCkkV2VpZ2h0LCBuY29sID0gNikpDQogIGNvbG5hbWVzKHRlbXBfaW1wb3J0YW5jZSkgPC0gYygiRmVhdHVyZSIsIHBhc3RlMCgiTGFiZWxfIiwgMTo2KSkNCiAgdGVtcF9pbXBvcnRhbmNlW1siU2lnbiJdXSA8LSBwYXN0ZTAoaWZlbHNlKHRlbXBfaW1wb3J0YW5jZVtbMl1dID49IDAsICIrIiwgIi0iKSwgaWZlbHNlKHRlbXBfaW1wb3J0YW5jZVtbM11dID49IDAsICIrIiwgIi0iKSwgaWZlbHNlKHRlbXBfaW1wb3J0YW5jZVtbNF1dID49IDAsICIrIiwgIi0iKSwgaWZlbHNlKHRlbXBfaW1wb3J0YW5jZVtbNV1dID49IDAsICIrIiwgIi0iKSwgaWZlbHNlKHRlbXBfaW1wb3J0YW5jZVtbNl1dID49IDAsICIrIiwgIi0iKSwgaWZlbHNlKHRlbXBfaW1wb3J0YW5jZVtbN11dID49IDAsICIrIiwgIi0iKSkNCiAgdGVtcF9pbXBvcnRhbmNlWywgMjo3XSA8LSBhYnModGVtcF9pbXBvcnRhbmNlWywgMjo3LCB3aXRoID0gRkFMU0VdKQ0KICB0ZW1wX21lYW5zW1sxNCAtIGldXSA8LSByb3dNZWFucyh0ZW1wX2ltcG9ydGFuY2VbLCAyOjcsIHdpdGggPSBGQUxTRV0pDQogIHRlbXBfaW1wb3J0YW5jZVtbcGFzdGUwKCJGb2xkXyIsIDEzIC0gaSwgIl9NZWFuIildXSA8LSB0ZW1wX21lYW5zW1sxNCAtIGldXQ0KICANCiAgIyBFbnJlZ2lzdHJlbWVudCBzb3VzIGZvcm1lIGRlIHRhYmxlYXUgaW50ZXJhY3RpZg0KICB0ZW1wX2R0W1tpIC0gOV1dIDwtIGRhdGF0YWJsZSh0ZW1wX2ltcG9ydGFuY2UsDQogICAgICAgIGZpbHRlciA9ICJ0b3AiLCAjIEZpbHRyYWdlIGF1LWRlc3N1cyBkZSBsYSB0YWJsZQ0KICAgICAgICBjbGFzcyA9ICJjZWxsLWJvcmRlciBzdHJpcGUiLCAjIENTUw0KICAgICAgICBleHRlbnNpb25zID0gYygiQ29sUmVvcmRlciIsDQogICAgICAgICAgICAgICAgICAgICAgICJSb3dSZW9yZGVyIiksICMgUmVvcmRvbm5lciBtYW51ZWxsZW1lbnQgw6AgbGEgbWFpbg0KICAgICAgICBvcHRpb25zID0gbGlzdChwYWdlTGVuZ3RoID0gMTMsICMgUGFnZSBhZmZpY2hhbnQgMTMgbGlnbmVzDQogICAgICAgICAgICAgICAgICAgICAgIG9yZGVyID0gbGlzdChsaXN0KDksICJkZXNjIikpLCAjIE9yZG9ubmVyIHBhciBkw6lmYXV0IHBhciBsZXMgZmFjdGV1cnMgYXlhbnQgbGUgcG9pZHMgbGUgcGx1cyBncm9zDQogICAgICAgICAgICAgICAgICAgICAgIGNvbFJlb3JkZXIgPSBUUlVFLCAjIFBsdWdpbg0KICAgICAgICAgICAgICAgICAgICAgICByb3dSZW9yZGVyID0gVFJVRSkpICU+JSAjIFBsdWdpbg0KICBmb3JtYXRTdHlsZShwYXN0ZTAoIkxhYmVsXyIsIDE6NiksDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihyYW5nZSh0ZW1wX2ltcG9ydGFuY2VbLCAyOjcsIHdpdGggPSBGQUxTRV0pLCAnbGlnaHRibHVlJyksICMgQ291bGV1ciBibGV1ZSBwb3VyIGxlIGNvZWZmaWNpZW50DQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kU2l6ZSA9ICcxMDAlIDkwJScsDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kUG9zaXRpb24gPSAnY2VudGVyJykgJT4lDQogIGZvcm1hdFN0eWxlKHBhc3RlMCgiRm9sZF8iLCAxMyAtIGksICJfTWVhbiIpLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihyYW5nZSh0ZW1wX2ltcG9ydGFuY2VbWzldXSksICdwaW5rJyksICMgQ291bGV1ciByb3NlIHBvdXIgbGEgbcOpdHJpcXVlIGRlIG1veWVubmUNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFNpemUgPSAnMTAwJSA5MCUnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgIGJhY2tncm91bmRQb3NpdGlvbiA9ICdjZW50ZXInKSAlPiUNCiAgZm9ybWF0Um91bmQoY29sdW1ucyA9IGMocGFzdGUwKCJMYWJlbF8iLCAxOjYpLCBwYXN0ZTAoIkZvbGRfIiwgMTMgLSBpLCAiX01lYW4iKSksDQogICAgICAgICAgICAgIGRpZ2l0cyA9IDYpDQogIA0KfQ0KDQojIENhbGN1bCBkdSBwb2lkcyBtb3llbiBhZmZlY3TDqSDDoCBjaGFxdWUgZmVhdHVyZQ0KdGVtcF9tZWFuc1tbNV1dIDwtIHJvd01lYW5zKHRlbXBfbWVhbnNbLCAyOjRdKSAjIFBvaWRzIG1veWVuDQp0ZW1wX21lYW5zW1s2XV0gPC0gYXBwbHkobWluaV9sbVssIHdoaWNoKGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkZGlzY3JldGUgPT0gMSldLCAyLCBmdW5jdGlvbih4KSB7bWVhbih4KX0pICMgTW95ZW5uZSBkZSBsYSBmZWF0dXJlIGRhbnMgbGVzIGRvbm7DqWVzDQp0ZW1wX21lYW5zW1s3XV0gPC0gYXBwbHkobWluaV9sbVssIHdoaWNoKGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkZGlzY3JldGUgPT0gMSldLCAyLCBmdW5jdGlvbih4KSB7c2QoeCl9KSAjIEVjYXJ0LXR5cGUgZGUgbGEgZmVhdHVyZSBkYW5zIGxlcyBkb25uw6llcw0KDQojIFByw6lwcmF0aW9uIGR1IHRhYmxlYXUgaW50ZXJhY3RpZiBzdXIgbGVzIHBvaWRzIG1veWVucyBhZ3LDqWfDqXMNCnRlbXBfZHRbWzRdXSA8LSBkYXRhdGFibGUodGVtcF9tZWFucywNCiAgICAgICAgZmlsdGVyID0gInRvcCIsICMgRmlsdHJhZ2UgYXUtZGVzc3VzIGRlIGxhIHRhYmxlDQogICAgICAgIGNsYXNzID0gImNlbGwtYm9yZGVyIHN0cmlwZSIsICMgQ1NTDQogICAgICAgIGV4dGVuc2lvbnMgPSBjKCJDb2xSZW9yZGVyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgIlJvd1Jlb3JkZXIiKSwgIyBSZW9yZG9ubmVyIG1hbnVlbGxlbWVudCDDoCBsYSBtYWluDQogICAgICAgIG9wdGlvbnMgPSBsaXN0KHBhZ2VMZW5ndGggPSAxMywgIyBQYWdlIGFmZmljaGFudCAxMyBsaWduZXMNCiAgICAgICAgICAgICAgICAgICAgICAgb3JkZXIgPSBsaXN0KGxpc3QoNSwgImRlc2MiKSksICMgT3Jkb25uZXIgcGFyIGTDqWZhdXQgcGFyIGxlcyBmYWN0ZXVycyBheWFudCBsZSBwb2lkcyBsZSBwbHVzIGdyb3MgZW4gbW95ZW5uZQ0KICAgICAgICAgICAgICAgICAgICAgICBjb2xSZW9yZGVyID0gVFJVRSwgIyBQbHVnaW4NCiAgICAgICAgICAgICAgICAgICAgICAgcm93UmVvcmRlciA9IFRSVUUpKSAlPiUgIyBQbHVnaW4NCiAgZm9ybWF0U3R5bGUocGFzdGUwKCJGb2xkXyIsIDE6MyksDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihyYW5nZSh0ZW1wX21lYW5zWywgMjo0XSksICdsaWdodGJsdWUnKSwgIyBDb3VsZXVyIGJsZXVlIHBvdXIgbGUgY29lZmZpY2llbnQNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRTaXplID0gJzEwMCUgOTAlJywNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRSZXBlYXQgPSAnbm8tcmVwZWF0JywNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRQb3NpdGlvbiA9ICdjZW50ZXInKSAlPiUNCiAgZm9ybWF0U3R5bGUoIkZvbGRfTWVhbiIsDQogICAgICAgICAgICAgIGJhY2tncm91bmQgPSBzdHlsZUNvbG9yQmFyKHJhbmdlKHRlbXBfbWVhbnNbWzVdXSksICdwaW5rJyksICMgQ291bGV1ciByb3NlIHBvdXIgbGEgbcOpdHJpcXVlIGRlIG1veWVubmUNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFNpemUgPSAnMTAwJSA5MCUnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgIGJhY2tncm91bmRQb3NpdGlvbiA9ICdjZW50ZXInKSAlPiUNCiAgZm9ybWF0U3R5bGUoIkZlYXR1cmVfTWVhbiIsDQogICAgICAgICAgICAgIGJhY2tncm91bmQgPSBzdHlsZUNvbG9yQmFyKHJhbmdlKHRlbXBfbWVhbnNbWzZdXSksICdsaWdodGdyZWVuJyksICMgQ291bGV1ciB2ZXJ0ZSBwb3VyIGxhIG1veWVubmUgZGVzIGZlYXR1cmVzDQogICAgICAgICAgICAgIGJhY2tncm91bmRTaXplID0gJzEwMCUgOTAlJywNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFJlcGVhdCA9ICduby1yZXBlYXQnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUG9zaXRpb24gPSAnY2VudGVyJykgJT4lDQogIGZvcm1hdFN0eWxlKCJGZWF0dXJlX1NEIiwNCiAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IHN0eWxlQ29sb3JCYXIocmFuZ2UodGVtcF9tZWFuc1tbN11dKSwgJ29yYW5nZScpLCAjIENvdWxldXIgdmVydGUgcG91ciBsJ8OpY2FydC10eXBlIGRlcyBmZWF0dXJlcw0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kU2l6ZSA9ICcxMDAlIDkwJScsDQogICAgICAgICAgICAgIGJhY2tncm91bmRSZXBlYXQgPSAnbm8tcmVwZWF0JywNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFBvc2l0aW9uID0gJ2NlbnRlcicpICU+JQ0KICBmb3JtYXRSb3VuZChjb2x1bW5zID0gYyhwYXN0ZTAoIkZvbGRfIiwgMTozKSwgIkZvbGRfTWVhbiIsICJGZWF0dXJlX01lYW4iLCAiRmVhdHVyZV9TRCIpLA0KICAgICAgICAgICAgICBkaWdpdHMgPSA2KQ0KDQojIETDqXBpdm90YWdlIGR1IGxvZw0KZXZvbHV0aW9uIDwtIHJiaW5kbGlzdChldm9sdXRpb24pDQpjb2xuYW1lcyhldm9sdXRpb24pIDwtIGMoIkl0ZXJhdGlvbiIsICJFeGFjdGl0dWRlIiwgIkZvbGQiKQ0KZXZvbHV0aW9uJEV4YWN0aXR1ZGUgPC0gMSAtIGV2b2x1dGlvbiRFeGFjdGl0dWRlDQpldm9sdXRpb24kRm9sZCA8LSBhcy5mYWN0b3IoZXZvbHV0aW9uJEZvbGQpDQoNCiMgUHLDqWRpY3Rpb24gw6AgcGFydGlyIGRlcyBwcm9iYWJpbGl0w6lzDQpwcmVkaWN0ZWRMYWJlbCA8LSBkYXRhLmZyYW1lKExhYmVsID0gZ3JvdXBfcGF0aCRwYXRoX0lELCBQcmVkaWN0aW9uID0gYXBwbHkocHJlZGljdGVkVmFsdWVzLCAxLCBmdW5jdGlvbih4KSB7d2hpY2gubWF4KHgpfSkpDQoNCnRpbWluZyhDdXJyZW50VGltZSwgIlByw6lwYXJhdGlvbiBkZSBsJ2FuYWx5c2UgZHUgZGVybmllciBtb2TDqGxlIGRlIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUiKQ0KDQojIEFmZmljaGFnZSBkZSBsJ8Opdm9sdXRpb24gZGUgbGEgcGVyZm9ybWFuY2UgZHUgbW9kw6hsZSBzZWxvbiBsZSBub21icmUgZCdpdMOpcmF0aW9uLCBzb3VzIGZvcm1lIGRlIHBsb3QgaW50ZXJhY3RpZg0KZ2dwbG90bHkoZ2dwbG90KGRhdGEgPSBldm9sdXRpb24sIGFlc19zdHJpbmcoeCA9ICJJdGVyYXRpb24iLCB5ID0gIkV4YWN0aXR1ZGUiLCBncm91cCA9ICJGb2xkIiwgY29sb3IgPSAiRm9sZCIpKSArIGdlb21fbGluZSgpICsgZ2VvbV9wb2ludCgpICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MiIpICsgdGhlbWVfYncoKSArIGxhYnModGl0bGUgPSAiRXZvbHV0aW9uIGRlIGwnZXhhY3RpdHVkZSBwYXIgcmFwcG9ydCBhdSBub21icmUgZCdpdMOpcmF0aW9ucyBkJ2VudHJhaW5lbWVudCIpLCB3aWR0aCA9IDk2MCwgaGVpZ2h0ID0gNzIwKQ0KDQojIEFmZmljaGFnZSBkZSBsYSBtYXRyaWNlIGRlIGNvbmZ1c2lvbiBzb3VzIGZvcm1lIGRlIHBsb3QgaW50ZXJhY3RpZg0KY29uZnVzaW9uX21hdCA8LSBleHBhbmQuZ3JpZChMYWJlbCA9IDE6NiwgUHJlZGljdGlvbiA9IDE6NikNCmNvbmZ1c2lvbl9tYXQgPC0gbWVyZ2UoY29uZnVzaW9uX21hdCwgZGF0YS50YWJsZShwcmVkaWN0ZWRMYWJlbClbLCBsaXN0KEZyZXEgPSBzdW0oLk4pKSwgYnkgPSBsaXN0KExhYmVsLCBQcmVkaWN0aW9uKV0sIGJ5ID0gYygiTGFiZWwiLCAiUHJlZGljdGlvbiIpLCBhbGwueCA9IFRSVUUpDQpjb25mdXNpb25fbWF0W1siRnJlcSJdXVtpcy5uYShjb25mdXNpb25fbWF0W1siRnJlcSJdXSldIDwtIDANCmNvbmZ1c2lvbl9tYXRbWyJMYWJlbCJdXSA8LSBhcy5mYWN0b3IoY29uZnVzaW9uX21hdFtbIkxhYmVsIl1dKQ0KY29uZnVzaW9uX21hdFtbIlByZWRpY3Rpb24iXV0gPC0gYXMuZmFjdG9yKGNvbmZ1c2lvbl9tYXRbWyJQcmVkaWN0aW9uIl1dKQ0KZ2dwbG90bHkoZ2dwbG90KCkgKyBnZW9tX3JlY3QoZGF0YSA9IGRhdGEuZnJhbWUoY2VudCA9IDE6NiksIHNpemUgPSAyLCBmaWxsID0gTkEsIGNvbG91ciA9ICJibGFjayIsIGFlcyh4bWluID0gY2VudCAtIDAuNSwgeG1heCA9IGNlbnQgKyAwLjUsIHltaW4gPSBjZW50IC0gMC41LCB5bWF4ID0gY2VudCArIDAuNSkpICsgZ2VvbV90aWxlKGRhdGEgPSBjb25mdXNpb25fbWF0LCBhZXNfc3RyaW5nKHggPSAiTGFiZWwiLCB5ID0gIlByZWRpY3Rpb24iLCBmaWxsID0gIkZyZXEiKSkgKyBnZW9tX3RleHQoZGF0YSA9IGNvbmZ1c2lvbl9tYXQsIGFlc19zdHJpbmcoeCA9ICJMYWJlbCIsIHkgPSAiUHJlZGljdGlvbiIsIGxhYmVsID0gIkZyZXEiKSkgKyBzY2FsZV94X2Rpc2NyZXRlKG5hbWUgPSAiVHJhamVjdG9pcmUgUsOpZWxsZSIpICsgc2NhbGVfeV9kaXNjcmV0ZShuYW1lID0gIlRyYWplY3RvaXJlIFByw6lkaXRlIikgKyBzY2FsZV9maWxsX2dyYWRpZW50bihjb2xvdXJzID0gcmV2KGJyZXdlci5wYWxfZXh0ZW5kZWQoMywgIlBpWUciKSkpICsgbGFicyh0aXRsZSA9ICJNYXRyaWNlIGRlIENvbmZ1c2lvbiBkZSBsYSBUcmFqZWN0b2lyZSIsIGZpbGwgPSAiRnLDqXF1ZW5jZSIpLCB3aWR0aCA9IDk2MCwgaGVpZ2h0ID0gNzIwKQ0KDQojIEFmZmljaGFnZSBkZXMgdGFibGVzIMOgIGxhIGZpbiBjYXIgbGUgZm9ybWF0dGFnZSBwb3Nzw6hkZSB1biBidWcgaW5ow6lyZW50IGxvcnNxdSdvbiBhIHBsdXNpZXVycyBkYXRhdGFibGVzIChEVCkgZGFucyBsZSBtw6ptZSBjaHVuaw0KIyBodG1sdG9vbHM6OnRhZ0xpc3QodGVtcF9kdFtbM11dLCB0ZW1wX2R0W1syXV0sIHRlbXBfZHRbWzFdXSwgdGVtcF9kdFtbNF1dKQ0KYGBgDQoNCmBgYHtyIFRlbXA5LCBlY2hvPUZBTFNFfQ0KdGVtcF9kdFtbM11dICMgQ29lZmZpY2llbnRzIGNvbnRyZSBsYSBzYWxsZSAzDQpgYGANCg0KYGBge3IgVGVtcDEwLCBlY2hvPUZBTFNFfQ0KdGVtcF9kdFtbMl1dICMgQ29lZmZpY2llbnRzIGNvbnRyZSBsYSBzYWxsZSAyDQpgYGANCg0KYGBge3IgVGVtcDExLCBlY2hvPUZBTFNFfQ0KdGVtcF9kdFtbMV1dICMgQ29lZmZpY2llbnRzIGNvbnRyZSBsYSBzYWxsZSAxDQpgYGANCg0KYGBge3IgVGVtcDEyLCBlY2hvPUZBTFNFfQ0KdGVtcF9kdFtbNF1dICMgQ29lZmZpY2llbnRzIGFncsOpZ8Opcw0KYGBgDQoNCiMgQ29uY2x1c2lvbg0KDQpOb3VzIGF2b25zIGZpbmkgbGUgZMOpYnV0IGRlIHRhY2hlIGQnYW5hbHlzZSBhcHLDqHMgNiBoZXVyZXMgZGUgdHJhdmFpbC4gQ2Ugbidlc3QgcXUndW4gZMOpYnV0IGQnZXhwbG9yYXRpb24sIGV0IGxlcyBwZXJmb3JtYW5jZXMgcGV1dmVudCDDqnRyZSBiaWVuIG1laWxsZXVyZXMgcGFyIGxhIGNyw6lhdGlvbiBkZSBmZWF0dXJlcyBkaWZmw6lyZW50ZXMuIE5vdXMgYXVyaW9ucyBwdSBwYXIgZXhlbXBsZSBmb3JjZXIgZGl2ZXJzZXMgaW50ZXJhY3Rpb25zIGVudHJlIGxlcyB2YXJpYWJsZXMgcGFyIGRlcyBtdWx0aXBsaWNhdGlvbnMsIG91IGVuY29yZSBjcsOpw6kgZGVzIGZlYXR1cmVzIGQndW5lIGF1dHJlIG1hbmnDqHJlIChleGVtcGxlcyA6IHByw6lkaWN0aW9uIGRlcyBwcm9jaGFpbnMgcG9pbnRzIHBhciBzw6lyaWUgdGVtcG9yZWxsZSwgdXRpbGlzYXRpb24gdW5pcXVlbWVudCBkJ3VuIG5vbWJyZSByZXN0cmVpbnQgZGUgcG9pbnRzIHBvdXIgbGUgY2FsY3VsIGRlcyBmZWF0dXJlcywgdXRpbGlzYXRpb24gZGVzIFggZGVybmllcnMgcG9pbnRzIGNvbW1lIGZlYXR1cmVzLi4uKS4NCg0KQ29tbWUgcHLDqXZ1IGR1cmFudCBsYSBzZWNvbmRlIGFuYWx5c2UgZXhwbG9yYXRvaXJlIGF2ZWMgbGEgZMOpbW9uc3RyYXRpb24gZHUgcHJvYmzDqG1lIGRlIGRvbWFpbmVzIGRlIGTDqWZpbml0aW9uIGRlcyB2YXJpYWJsZXMgKHByb2Jsw6htZSBkZSBsaW7DqWFyaXTDqSksIGxlcyBtb2TDqGxlcyBsaW7DqWFpcmVzIHMnZW4gc29ydGVudCBsZXMgbWVpbGxldXJzIMOgIGxhIGZpbi4gTGVzIG1vZMOobGVzIG5vbi1saW7DqWFpcmVzIHNvbnQgw6AgbGEgdHJhaW5lIGVuIHBlcmZvcm1hbmNlLCB2dSBxdWUgOg0KDQotIFBvdXIgdG91dCBwcm9ibMOobWUgZGUgbW9kw6lsaXNhdGlvbiwgaWwgZXN0IHBsdXMgZmFjaWxlIGQnYXBwcmVuZHJlIHVuIHByb2Jsw6htZSBsaW7DqWFpcmUgcXUndW4gcHJvYmzDqG1lIG5vbi1saW7DqWFpcmUNCi0gUG91ciB0b3V0IHByb2Jsw6htZSBkZSBtb2TDqWxpc2F0aW9uLCBzaSBsZSBwcm9ibMOobWUgc2UgcmFwcHJvY2hlIGQndW4gcHJvYmzDqG1lIGxpbsOpYWlyZSwgYWxvcnMgbGEgcGVyZm9ybWFuY2UgZCd1biBtb2TDqGxlIGxpbsOpYWlyZSBzZXJhIHN1cMOpcmlldXJlIMOgIGxhIHBlcmZvcm1hbmNlIGQndW4gbW9kw6hsZSBub24tbGluw6lhaXJlIGRhbnMgbGEgcGx1cGFydCBkZXMgY2FzDQoNCkRlIGZhaXQsIGwnaW1wb3J0YW50IGVzdCBkZSByZXRlbmlyIHF1ZSBsYSBwZXJmb3JtYW5jZSBkdSBtb2TDqGxlIGTDqXBlbmQgZGFucyBsJ29yZHJlIGRlIHByaW9yaXTDqSA6DQoNCi0gTGEgcGx1cyBpbXBvcnRhbnRlIDogbGVzIGZlYXR1cmVzDQotIFBldSBpbXBvcnRhbnQgOiBsZXMgaHlwZXJwYXJhbcOodHJlcw0KDQpWb2ljaSBsZSBzb21tYWlyZSBkZXMgcsOpc3VsdGF0cyBsb3JzcXUnb24gYSBlbnRyYWluw6kgYXZlYyBkZXV4IGRhdGFzZXRzIGNvbnRyZSB1bmUgZGF0YXNldCA6DQoNCmBgYHtyIEFncmVnYXRpb25TY29yZXN9DQojIENoYXJnZW1lbnQgZGVzIHNjb3JlcyBlbnJlZ2lzdHLDqXMgYXZlYyBuZXR0b3lhZ2UNCmFjY3VyYWN5MSA8LSB0KGZyZWFkKCJzY29yZXMvMV9tb2RlbHMuY3N2IilbMTUsIC0xXVsxLCBjKDEsIDUsIDIsIDYsIDMsIDcsIDQsIDgsIDk6MTIpXSkNCmFjY3VyYWN5MiA8LSB0KGZyZWFkKCJzY29yZXMvMl9tb2RlbHMuY3N2IilbMTUsIC0xXVsxLCBjKDEsIDUsIDIsIDYsIDMsIDcsIDQsIDgsIDk6MTIpXSkNCmFjY3VyYWN5MyA8LSB0KGZyZWFkKCJzY29yZXMvM19tb2RlbHMuY3N2IilbMTUsIC0xXVsxLCBjKDEsIDUsIDIsIDYsIDMsIDcsIDQsIDgsIDk6MTIpXSkNCmFjY3VyYWN5NCA8LSB0KGZyZWFkKCJzY29yZXMvNF9tb2RlbHMuY3N2IilbMTUsIC0xXVsxLCBjKDEsIDUsIDIsIDYsIDMsIDcsIDQsIDgsIDk6MTIpXSkNCg0KIyBBZ3LDqWdhdGlvbiBkZXMgc2NvcmVzDQphY2N1cmFjeV9hZ2cgPC0gZGF0YS50YWJsZShEZWZhdXQgPSBjKGFjY3VyYWN5MVsxOjJdLCBtZWFuKGFjY3VyYWN5MVs5OjEwXSksIG1lYW4oYWNjdXJhY3kxWzExOjEyXSksIGFjY3VyYWN5MVszOjhdKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIEFwcHJveGltYXRpb24gPSBjKGFjY3VyYWN5MlsxOjJdLCBtZWFuKGFjY3VyYWN5Mls5OjEwXSksIG1lYW4oYWNjdXJhY3kyWzExOjEyXSksIGFjY3VyYWN5MlszOjhdKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIEludmVyc2VtZW50ID0gYyhhY2N1cmFjeTNbMToyXSwgbWVhbihhY2N1cmFjeTNbOToxMF0pLCBtZWFuKGFjY3VyYWN5M1sxMToxMl0pLCBhY2N1cmFjeTNbMzo4XSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICBTZWxlY3Rpb24gPSBjKGFjY3VyYWN5NFsxOjJdLCBtZWFuKGFjY3VyYWN5NFs5OjEwXSksIG1lYW4oYWNjdXJhY3k0WzExOjEyXSksIGFjY3VyYWN5NFszOjhdKSkNCmFjY3VyYWN5X2FnZ1tbIkV2b2x1dGlvbiJdXSA8LSBhcHBseShhY2N1cmFjeV9hZ2csIDEsIGZ1bmN0aW9uKHgpIHttYXgoeCkgLSBtaW4oeCl9KQ0Kcm93Lm5hbWVzKGFjY3VyYWN5X2FnZykgPC0gYygiUsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSAoeGdiKSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJSw6lncmVzc2lvbiBsb2dpc3RpcXVlIChoMm8pIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlLDqXNlYXUgZGUgbmV1cm9uZXMgMzJ4NiAoaDJvKSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJSw6lzZWF1IGRlIG5ldXJvbmVzIDE2eDE2eDYgKGgybykiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQXJicmUgZGUgZMOpY2lzaW9uICh4Z2IpIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkFyYnJlIGRlIGTDqWNpc2lvbiAoaDJvKSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJGb3LDqnQgYWzDqWF0b2lyZSAoeGdiKSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJGb3LDqnQgYWzDqWF0b2lyZSAoaDJvKSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJBcmJyZXMgYm9vc3TDqXMgKHhnYikiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQXJicmVzIGJvb3N0w6lzIChoMm8pIikNCg0KIyBBZmZpY2hhZ2UgZGVzIHNjb3JlcyBkYW5zIHVuIHRhYmxlYXUgaW50ZXJhY3RpZg0KZGF0YXRhYmxlKGFjY3VyYWN5X2FnZywNCiAgICAgICAgICBmaWx0ZXIgPSAidG9wIiwgIyBGaWx0cmFnZSBhdS1kZXNzdXMgZGUgbGEgdGFibGUNCiAgICAgICAgICBjbGFzcyA9ICJjZWxsLWJvcmRlciBzdHJpcGUiLCAjIENTUw0KICAgICAgICAgIGV4dGVuc2lvbnMgPSBjKCJDb2xSZW9yZGVyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAiUm93UmVvcmRlciIpLCAjIFJlb3Jkb25uZXIgbWFudWVsbGVtZW50IMOgIGxhIG1haW4NCiAgICAgICAgICBvcHRpb25zID0gbGlzdChwYWdlTGVuZ3RoID0gMTAsICMgUGFnZSBhZmZpY2hhbnQgMTAgbGlnbmVzDQogICAgICAgICAgICAgICAgICAgICAgICAgY29sUmVvcmRlciA9IFRSVUUsICMgUGx1Z2luDQogICAgICAgICAgICAgICAgICAgICAgICAgcm93UmVvcmRlciA9IFRSVUUpKSAlPiUgIyBQbHVnaW4NCiAgZm9ybWF0U3R5bGUoYygiRGVmYXV0IiwgIkFwcHJveGltYXRpb24iLCAiSW52ZXJzZW1lbnQiLCAiU2VsZWN0aW9uIiksDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihjKDAsIG1heChhY2N1cmFjeV9hZ2dbWyJTZWxlY3Rpb24iXV0pKSwgJ2xpZ2h0Z3JlZW4nKSwgIyBDb3VsZXVyIHZlcnQgY2xhaXIgcG91ciBsZXMgbcOpdHJpcXVlcyBwYXIgZm9sZA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZFNpemUgPSAnMTAwJSA5MCUnLA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZFJlcGVhdCA9ICduby1yZXBlYXQnLA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZFBvc2l0aW9uID0gJ2NlbnRlcicpICU+JQ0KICBmb3JtYXRTdHlsZShjKCJFdm9sdXRpb24iKSwNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmQgPSBzdHlsZUNvbG9yQmFyKGMoMCwgbWF4KGFjY3VyYWN5X2FnZ1tbIkV2b2x1dGlvbiJdXSkpLCAncGluaycpLCAjIENvdWxldXIgcm9zZSBwb3VyIGwnw6l2b2x1dGlvbiBkZSBsYSBtw6l0cmlxdWUNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRTaXplID0gJzEwMCUgOTAlJywNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRSZXBlYXQgPSAnbm8tcmVwZWF0JywNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRQb3NpdGlvbiA9ICdjZW50ZXInKSAlPiUNCiAgZm9ybWF0UGVyY2VudGFnZShjb2x1bW5zID0gYygiRGVmYXV0IiwgIkFwcHJveGltYXRpb24iLCAiSW52ZXJzZW1lbnQiLCAiU2VsZWN0aW9uIiwgIkV2b2x1dGlvbiIpLA0KICAgICAgICAgICAgICBkaWdpdHMgPSA0KQ0KYGBgDQoNCkVuIGVmZmV0LCBvbiBuZSBwZXV0IHBhcyBmYWlyZSBhcHByZW5kcmUgw6AgdW4gYWxnb3JpdGhtZSBjb21tZW50IGNyw6llciB1bmUgZmVhdHVyZSBxdWkgbidleGlzdGUgcGFzIGVuIHRhbnQgcXUnZW50csOpZS4NCg0KSWwgZXN0IHRvdXQgw6AgZmFpdCBwb3NzaWJsZSBxdSd1biByw6lzZWF1IGRlIG5ldXJvbmVzIGF2ZWMgdW5lIGFyY2hpdGVjdHVyZSBwbHVzIGF2YW5jw6llIHB1aXNzZSBmYWlyZSBiZWF1Y291cCBtaWV1eCwgdW5pcXVlbWVudCBlbiB1dGlsaXNhbnQgbGVzIGZlYXR1cmVzIGluaXRpYWxlcy4gQydlc3QgY2UgcXVlIEJhY2NpdSBldCBhbC4gb250IHLDqWFsaXPDqSBkYW5zICpBbiBleHBlcmltZW50YWwgY2hhcmFjdGVyaXphdGlvbiBvZiByZXNlcnZvaXIgY29tcHV0aW5nIGluIGFtYmllbnQgYXNzaXN0ZWQgbGl2aW5nIGFwcGxpY2F0aW9ucyogc3DDqWNpZmlxdWVtZW50IHBvdXIgcHLDqWRpcmUgbGUgY2hhbmdlbWVudCBkZSB6b25lIGRhbnMgdW5lIHNhbGxlIChqdXNxdSfDoCA5OSUgZCdleGFjdGl0dWRlKS4=